/*
 AngularJS
 (c) 2010-2020 Google LLC. http://angularjs.org
 License: MIT
 XLTS for AngularJS v1.5.13
 (c) 2021 XLTS.dev All Rights Reserved. https://xlts.dev/angularjs
 License: Obtain a commercial license from XLTS.dev before using this software.
*/
(function (w) {
    'use strict'; function G(a, b) { b = b || Error; return function () { var d = arguments[0], c; c = "[" + (a ? a + ":" : "") + d + "] http://errors.angularjs.xlts.dev/1.5.13/" + (a ? a + "/" : "") + d; for (d = 1; d < arguments.length; d++) { c = c + (1 == d ? "?" : "&") + "p" + (d - 1) + "="; var f = encodeURIComponent, e; e = arguments[d]; e = "function" == typeof e ? e.toString().replace(/ \{[\s\S]*$/, "") : "undefined" == typeof e ? "undefined" : "string" != typeof e ? JSON.stringify(e) : e; c += f(e) } return new b(c) } } function la(a) {
        if (null == a || Ya(a)) return !1; if (I(a) || C(a) || F && a instanceof
            F) return !0; var b = "length" in Object(a) && a.length; return ba(b) && (0 <= b && (b - 1 in a || a instanceof Array) || "function" === typeof a.item)
    } function q(a, b, d) {
        var c, f; if (a) if (D(a)) for (c in a) "prototype" === c || "length" === c || "name" === c || a.hasOwnProperty && !a.hasOwnProperty(c) || b.call(d, a[c], c, a); else if (I(a) || la(a)) { var e = "object" !== typeof a; c = 0; for (f = a.length; c < f; c++)(e || c in a) && b.call(d, a[c], c, a) } else if (a.forEach && a.forEach !== q) a.forEach(b, d, a); else if (zc(a)) for (c in a) b.call(d, a[c], c, a); else if ("function" ===
            typeof a.hasOwnProperty) for (c in a) a.hasOwnProperty(c) && b.call(d, a[c], c, a); else for (c in a) ua.call(a, c) && b.call(d, a[c], c, a); return a
    } function Ac(a, b, d) { for (var c = Object.keys(a).sort(), f = 0; f < c.length; f++)b.call(d, a[c[f]], c[f]); return c } function Bc(a) { return function (b, d) { a(d, b) } } function oe() { return ++tb } function Sb(a, b, d) {
        for (var c = a.$$hashKey, f = 0, e = b.length; f < e; ++f) {
            var g = b[f]; if (E(g) || D(g)) for (var h = Object.keys(g), k = 0, l = h.length; k < l; k++) {
                var m = h[k], n = g[m]; d && E(n) ? ja(n) ? a[m] = new Date(n.valueOf()) :
                    Za(n) ? a[m] = new RegExp(n) : n.nodeName ? a[m] = n.cloneNode(!0) : Tb(n) ? a[m] = n.clone() : (E(a[m]) || (a[m] = I(n) ? [] : {}), Sb(a[m], [n], !0)) : a[m] = n
            }
        } c ? a.$$hashKey = c : delete a.$$hashKey; return a
    } function R(a) { return Sb(a, va.call(arguments, 1), !1) } function pe(a) { return Sb(a, va.call(arguments, 1), !0) } function Z(a) { return parseInt(a, 10) } function Ub(a, b) { return R(Object.create(a), b) } function y() { } function $a(a) { return a } function ga(a) { return function () { return a } } function Cc(a) { return D(a.toString) && a.toString !== ma } function z(a) {
        return "undefined" ===
            typeof a
    } function x(a) { return "undefined" !== typeof a } function E(a) { return null !== a && "object" === typeof a } function zc(a) { return null !== a && "object" === typeof a && !Dc(a) } function C(a) { return "string" === typeof a } function ba(a) { return "number" === typeof a } function ja(a) { return "[object Date]" === ma.call(a) } function D(a) { return "function" === typeof a } function Za(a) { return "[object RegExp]" === ma.call(a) } function Ya(a) { return a && a.window === a } function ab(a) { return a && a.$evalAsync && a.$watch } function Ka(a) {
        return "boolean" ===
            typeof a
    } function qe(a) { return a && ba(a.length) && re.test(ma.call(a)) } function Tb(a) { return !(!a || !(a.nodeName || a.prop && a.attr && a.find)) } function se(a) { var b = {}; a = a.split(","); var d; for (d = 0; d < a.length; d++)b[a[d]] = !0; return b } function wa(a) { return Q(a.nodeName || a[0] && a[0].nodeName) } function bb(a, b) { var d = a.indexOf(b); 0 <= d && a.splice(d, 1); return d } function sa(a, b) {
        function d(a, b) {
            var d = b.$$hashKey, e; if (I(a)) { e = 0; for (var f = a.length; e < f; e++)b.push(c(a[e])) } else if (zc(a)) for (e in a) b[e] = c(a[e]); else if (a &&
                "function" === typeof a.hasOwnProperty) for (e in a) a.hasOwnProperty(e) && (b[e] = c(a[e])); else for (e in a) ua.call(a, e) && (b[e] = c(a[e])); d ? b.$$hashKey = d : delete b.$$hashKey; return b
        } function c(a) { if (!E(a)) return a; var b = e.indexOf(a); if (-1 !== b) return g[b]; if (Ya(a) || ab(a)) throw xa("cpws"); var b = !1, c = f(a); void 0 === c && (c = I(a) ? [] : Object.create(Dc(a)), b = !0); e.push(a); g.push(c); return b ? d(a, c) : c } function f(a) {
            switch (ma.call(a)) {
                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 a.constructor(c(a.buffer),
                    a.byteOffset, a.length); case "[object ArrayBuffer]": if (!a.slice) { var b = new ArrayBuffer(a.byteLength); (new Uint8Array(b)).set(new Uint8Array(a)); return b } return a.slice(0); case "[object Boolean]": case "[object Number]": case "[object String]": case "[object Date]": return new a.constructor(a.valueOf()); case "[object RegExp]": return b = new RegExp(a.source, a.toString().match(/[^/]*$/)[0]), b.lastIndex = a.lastIndex, b; case "[object Blob]": return new a.constructor([a], { type: a.type })
            }if (D(a.cloneNode)) return a.cloneNode(!0)
        }
        var e = [], g = []; if (b) { if (qe(b) || "[object ArrayBuffer]" === ma.call(b)) throw xa("cpta"); if (a === b) throw xa("cpi"); I(b) ? b.length = 0 : q(b, function (a, d) { "$$hashKey" !== d && delete b[d] }); e.push(a); g.push(b); return d(a, b) } return c(a)
    } function na(a, b) {
        if (a === b) return !0; if (null === a || null === b) return !1; if (a !== a && b !== b) return !0; var d = typeof a, c; if (d === typeof b && "object" === d) if (I(a)) { if (!I(b)) return !1; if ((d = a.length) === b.length) { for (c = 0; c < d; c++)if (!na(a[c], b[c])) return !1; return !0 } } else {
            if (ja(a)) return ja(b) ? na(a.getTime(),
                b.getTime()) : !1; if (Za(a)) return Za(b) ? a.toString() === b.toString() : !1; if (ab(a) || ab(b) || Ya(a) || Ya(b) || I(b) || ja(b) || Za(b)) return !1; d = W(); for (c in a) if ("$" !== c.charAt(0) && !D(a[c])) { if (!na(a[c], b[c])) return !1; d[c] = !0 } for (c in b) if (!(c in d) && "$" !== c.charAt(0) && x(b[c]) && !D(b[c])) return !1; return !0
        } return !1
    } function cb(a, b, d) { return a.concat(va.call(b, d)) } function db(a, b) {
        var d = 2 < arguments.length ? va.call(arguments, 2) : []; return !D(b) || b instanceof RegExp ? b : d.length ? function () {
            return arguments.length ? b.apply(a,
                cb(d, arguments, 0)) : b.apply(a, d)
        } : function () { return arguments.length ? b.apply(a, arguments) : b.call(a) }
    } function te(a, b) { var d = b; "string" === typeof a && "$" === a.charAt(0) && "$" === a.charAt(1) ? d = void 0 : Ya(b) ? d = "$WINDOW" : b && w.document === b ? d = "$DOCUMENT" : ab(b) && (d = "$SCOPE"); return d } function eb(a, b) { if (!z(a)) return ba(b) || (b = b ? 2 : null), JSON.stringify(a, te, b) } function Ec(a) { return C(a) ? JSON.parse(a) : a } function Fc(a, b) { a = a.replace(ue, ""); var d = Date.parse("Jan 01, 1970 00:00:00 " + a) / 6E4; return ha(d) ? b : d } function Vb(a,
        b, d) { d = d ? -1 : 1; var c = a.getTimezoneOffset(); b = Fc(b, c); d *= b - c; a = new Date(a.getTime()); a.setMinutes(a.getMinutes() + d); return a } function ya(a) { a = F(a).clone(); try { a.empty() } catch (b) { } var d = F("<div>").append(a).html(); try { return a[0].nodeType === La ? Q(d) : d.match(/^(<[^>]+>)/)[1].replace(/^<([\w-]+)/, function (a, b) { return "<" + Q(b) }) } catch (c) { return Q(d) } } function Gc(a) { try { return decodeURIComponent(a) } catch (b) { } } function Hc(a) {
            var b = {}; q((a || "").split("&"), function (a) {
                var c, f, e; a && (f = a = a.replace(/\+/g, "%20"),
                    c = a.indexOf("="), -1 !== c && (f = a.substring(0, c), e = a.substring(c + 1)), f = Gc(f), x(f) && (e = x(e) ? Gc(e) : !0, ua.call(b, f) ? I(b[f]) ? b[f].push(e) : b[f] = [b[f], e] : b[f] = e))
            }); return b
        } function Wb(a) { var b = []; q(a, function (a, c) { I(a) ? q(a, function (a) { b.push(oa(c, !0) + (!0 === a ? "" : "=" + oa(a, !0))) }) : b.push(oa(c, !0) + (!0 === a ? "" : "=" + oa(a, !0))) }); return b.length ? b.join("&") : "" } function ub(a) { return oa(a, !0).replace(/%26/gi, "&").replace(/%3D/gi, "=").replace(/%2B/gi, "+") } function oa(a, b) {
            return encodeURIComponent(a).replace(/%40/gi,
                "@").replace(/%3A/gi, ":").replace(/%24/g, "$").replace(/%2C/gi, ",").replace(/%3B/gi, ";").replace(/%20/g, b ? "%20" : "+")
        } function ve(a, b) { var d, c, f = Oa.length; for (c = 0; c < f; ++c)if (d = Oa[c] + b, C(d = a.getAttribute(d))) return d; return null } function we(a, b) {
            var d, c, f = {}; q(Oa, function (b) { b += "app"; !d && a.hasAttribute && a.hasAttribute(b) && (d = a, c = a.getAttribute(b)) }); q(Oa, function (b) { b += "app"; var f; !d && (f = a.querySelector("[" + b.replace(":", "\\:") + "]")) && (d = f, c = f.getAttribute(b)) }); d && (xe ? (f.strictDi = null !== ve(d, "strict-di"),
                b(d, c ? [c] : [], f)) : w.console.error("AngularJS: disabling automatic bootstrap. <script> protocol indicates an extension, document.location.href does not match."))
        } function Ic(a, b, d) {
            E(d) || (d = {}); d = R({ strictDi: !1 }, d); var c = function () {
                a = F(a); if (a.injector()) { var c = a[0] === w.document ? "document" : ya(a); throw xa("btstrpd", c.replace(/</, "&lt;").replace(/>/, "&gt;")); } b = b || []; b.unshift(["$provide", function (b) { b.value("$rootElement", a) }]); d.debugInfoEnabled && b.push(["$compileProvider", function (a) { a.debugInfoEnabled(!0) }]);
                b.unshift("ng"); c = fb(b, d.strictDi); c.invoke(["$rootScope", "$rootElement", "$compile", "$injector", function (a, b, c, d) { a.$apply(function () { b.data("$injector", d); c(b)(a) }) }]); return c
            }, f = /^NG_ENABLE_DEBUG_INFO!/, e = /^NG_DEFER_BOOTSTRAP!/; w && f.test(w.name) && (d.debugInfoEnabled = !0, w.name = w.name.replace(f, "")); if (w && !e.test(w.name)) return c(); w.name = w.name.replace(e, ""); $.resumeBootstrap = function (a) { q(a, function (a) { b.push(a) }); return c() }; D($.resumeDeferredBootstrap) && $.resumeDeferredBootstrap()
        } function ye() {
            w.name =
            "NG_ENABLE_DEBUG_INFO!" + w.name; w.location.reload()
        } function ze(a) { a = $.element(a).injector(); if (!a) throw xa("test"); return a.get("$$testability") } function Jc(a, b) { b = b || "_"; return a.replace(Ae, function (a, c) { return (c ? b : "") + a.toLowerCase() }) } function Be() {
            var a; if (!Kc) {
                var b = vb(); (za = z(b) ? w.jQuery : b ? w[b] : void 0) && za.fn.on ? (F = za, R(za.fn, { scope: Pa.scope, isolateScope: Pa.isolateScope, controller: Pa.controller, injector: Pa.injector, inheritedData: Pa.inheritedData }), a = za.cleanData, za.cleanData = function (b) {
                    for (var c,
                        f = 0, e; null != (e = b[f]); f++)(c = za._data(e, "events")) && c.$destroy && za(e).triggerHandler("$destroy"); a(b)
                }) : F = S; $.element = F; Kc = !0
            }
        } function Ce() { S.htmlPrefilter = function (a) { var b = a.replace(De, "<$1></$2>"); w.console && w.console.warn && a !== b && w.console.warn("The following HTML string:\n\n" + a + "\n\nis now changed by AngularJS to:\n\n" + b + "\n\nThis will not happen when you stop calling UNSAFE_restoreLegacyJqLiteXHTMLReplacement; avoid self-closing tags."); return b } } function gb(a, b, d) {
            if (!a) throw xa("areq", b ||
                "?", d || "required"); return a
        } function Qa(a, b, d) { d && I(a) && (a = a[a.length - 1]); gb(D(a), b, "not a function, got " + (a && "object" === typeof a ? a.constructor.name || "Object" : typeof a)); return a } function Ra(a, b) { if ("hasOwnProperty" === a) throw xa("badname", b); } function Lc(a, b, d) { if (!b) return a; b = b.split("."); for (var c, f = a, e = b.length, g = 0; g < e; g++)c = b[g], a && (a = (f = a)[c]); return !d && D(a) ? db(f, a) : a } function wb(a) {
            for (var b = a[0], d = a[a.length - 1], c, f = 1; b !== d && (b = b.nextSibling); f++)if (c || a[f] !== b) c || (c = F(va.call(a, 0, f))), c.push(b);
            return c || a
        } function W() { return Object.create(null) } function Ee(a) {
            function b(a, b, c) { return a[b] || (a[b] = c()) } var d = G("$injector"), c = G("ng"); a = b(a, "angular", Object); a.$$minErr = a.$$minErr || G; return b(a, "module", function () {
                var a = {}; return function (e, g, h) {
                    if ("hasOwnProperty" === e) throw c("badname", "module"); g && a.hasOwnProperty(e) && (a[e] = null); return b(a, e, function () {
                        function a(b, d, e, f) { f || (f = c); return function () { f[e || "push"]([b, d, arguments]); return H } } function b(a, d) {
                            return function (b, f) {
                                f && D(f) && (f.$$moduleName =
                                    e); c.push([a, d, arguments]); return H
                            }
                        } if (!g) throw d("nomod", e); var c = [], f = [], r = [], s = a("$injector", "invoke", "push", f), H = {
                            _invokeQueue: c, _configBlocks: f, _runBlocks: r, requires: g, name: e, provider: b("$provide", "provider"), factory: b("$provide", "factory"), service: b("$provide", "service"), value: a("$provide", "value"), constant: a("$provide", "constant", "unshift"), decorator: b("$provide", "decorator"), animation: b("$animateProvider", "register"), filter: b("$filterProvider", "register"), controller: b("$controllerProvider",
                                "register"), directive: b("$compileProvider", "directive"), component: b("$compileProvider", "component"), config: s, run: function (a) { r.push(a); return this }
                        }; h && s(h); return H
                    })
                }
            })
        } function ka(a, b) { if (I(a)) { b = b || []; for (var d = 0, c = a.length; d < c; d++)b[d] = a[d] } else if (E(a)) for (d in b = b || {}, a) if ("$" !== d.charAt(0) || "$" !== d.charAt(1)) b[d] = a[d]; return b || a } function Fe(a) {
            R(a, {
                bootstrap: Ic, copy: sa, extend: R, merge: pe, equals: na, element: F, forEach: q, injector: fb, noop: y, bind: db, toJson: eb, fromJson: Ec, identity: $a, isUndefined: z,
                isDefined: x, isString: C, isFunction: D, isObject: E, isNumber: ba, isElement: Tb, isArray: I, version: Ge, isDate: ja, lowercase: Q, uppercase: xb, callbacks: { $$counter: 0 }, getTestability: ze, UNSAFE_restoreLegacyJqLiteXHTMLReplacement: Ce, $$minErr: G, $$csp: da, reloadWithDebugInfo: ye
            }); Xb = Ee(w); Xb("ng", ["ngLocale"], ["$provide", function (a) {
                a.provider({ $$sanitizeUri: He }); a.provider("$compile", Mc).directive({
                    a: Ie, input: Nc, textarea: Nc, form: Je, script: Ke, select: Le, option: Me, ngBind: Ne, ngBindHtml: Oe, ngBindTemplate: Pe, ngClass: Qe, ngClassEven: Re,
                    ngClassOdd: Se, ngCloak: Te, ngController: Ue, ngForm: Ve, ngHide: We, ngIf: Xe, ngInclude: Ye, ngInit: Ze, ngNonBindable: $e, ngPluralize: af, ngRepeat: bf, ngShow: cf, ngStyle: df, ngSwitch: ef, ngSwitchWhen: ff, ngSwitchDefault: gf, ngOptions: hf, ngTransclude: jf, ngModel: kf, ngList: lf, ngChange: mf, pattern: Oc, ngPattern: Oc, required: Pc, ngRequired: Pc, minlength: Qc, ngMinlength: Qc, maxlength: Rc, ngMaxlength: Rc, ngValue: nf, ngModelOptions: of
                }).directive({ ngInclude: pf }).directive(yb).directive(Sc); a.provider({
                    $anchorScroll: qf, $animate: rf, $animateCss: sf,
                    $$animateJs: tf, $$animateQueue: uf, $$AnimateRunner: vf, $$animateAsyncRun: wf, $browser: xf, $cacheFactory: yf, $controller: zf, $document: Af, $exceptionHandler: Bf, $filter: Tc, $$forceReflow: Cf, $interpolate: Df, $interval: Ef, $http: Ff, $httpParamSerializer: Gf, $httpParamSerializerJQLike: Hf, $httpBackend: If, $xhrFactory: Jf, $jsonpCallbacks: Kf, $location: Lf, $log: Mf, $parse: Nf, $rootScope: Of, $q: Pf, $$q: Qf, $sce: Rf, $sceDelegate: Sf, $sniffer: Tf, $templateCache: Uf, $templateRequest: Vf, $$testability: Wf, $timeout: Xf, $window: Yf, $$rAF: Zf,
                    $$jqLite: $f, $$HashMap: ag, $$cookieReader: bg
                })
            }])
        } function hb(a) { return a.replace(cg, function (a, d, c, f) { return f ? c.toUpperCase() : c }).replace(dg, "Moz$1") } function Uc(a) { a = a.nodeType; return 1 === a || !a || 9 === a } function Vc(a, b) {
            var d, c, f, e = b.createDocumentFragment(), g = [], h; if (Yb.test(a)) {
                d = e.appendChild(b.createElement("div")); c = (eg.exec(a) || ["", ""])[1].toLowerCase(); f = S.htmlPrefilter(a); if (10 > Ga) for (c = ib[c] || ib._default, d.innerHTML = c[1] + f + c[2], h = c[0]; h--;)d = d.firstChild; else {
                    c = ia[c] || []; for (h = c.length; -1 <
                        --h;)d.appendChild(w.document.createElement(c[h])), d = d.firstChild; d.innerHTML = f
                } g = cb(g, d.childNodes); d = e.firstChild; d.textContent = ""
            } else g.push(b.createTextNode(a)); e.textContent = ""; e.innerHTML = ""; q(g, function (a) { e.appendChild(a) }); return e
        } function Wc(a, b) { var d = a.parentNode; d && d.replaceChild(b, a); b.appendChild(a) } function S(a) {
            if (a instanceof S) return a; var b; C(a) && (a = Y(a), b = !0); if (!(this instanceof S)) { if (b && "<" !== a.charAt(0)) throw Zb("nosel"); return new S(a) } if (b) {
                b = w.document; var d; a = (d = fg.exec(a)) ?
                    [b.createElement(d[1])] : (d = Vc(a, b)) ? d.childNodes : []
            } Xc(this, a)
        } function $b(a) { return a.cloneNode(!0) } function zb(a, b) { b || jb(a); if (a.querySelectorAll) for (var d = a.querySelectorAll("*"), c = 0, f = d.length; c < f; c++)jb(d[c]) } function Yc(a, b, d, c) {
            if (x(c)) throw Zb("offargs"); var f = (c = Ab(a)) && c.events, e = c && c.handle; if (e) if (b) { var g = function (b) { var c = f[b]; x(d) && bb(c || [], d); x(d) && c && 0 < c.length || (a.removeEventListener(b, e, !1), delete f[b]) }; q(b.split(" "), function (a) { g(a); Bb[a] && g(Bb[a]) }) } else for (b in f) "$destroy" !==
                b && a.removeEventListener(b, e, !1), delete f[b]
        } function jb(a, b) { var d = a.ng339, c = d && kb[d]; c && (b ? delete c.data[b] : (c.handle && (c.events.$destroy && c.handle({}, "$destroy"), Yc(a)), delete kb[d], a.ng339 = void 0)) } function Ab(a, b) { var d = a.ng339, d = d && kb[d]; b && !d && (a.ng339 = d = ++gg, d = kb[d] = { events: {}, data: {}, handle: void 0 }); return d } function ac(a, b, d) { if (Uc(a)) { var c = x(d), f = !c && b && !E(b), e = !b; a = (a = Ab(a, !f)) && a.data; if (c) a[b] = d; else { if (e) return a; if (f) return a && a[b]; R(a, b) } } } function Cb(a, b) {
            return a.getAttribute ?
                -1 < (" " + (a.getAttribute("class") || "") + " ").replace(/[\n\t]/g, " ").indexOf(" " + b + " ") : !1
        } function Db(a, b) { b && a.setAttribute && q(b.split(" "), function (b) { a.setAttribute("class", Y((" " + (a.getAttribute("class") || "") + " ").replace(/[\n\t]/g, " ").replace(" " + Y(b) + " ", " "))) }) } function Eb(a, b) { if (b && a.setAttribute) { var d = (" " + (a.getAttribute("class") || "") + " ").replace(/[\n\t]/g, " "); q(b.split(" "), function (a) { a = Y(a); -1 === d.indexOf(" " + a + " ") && (d += a + " ") }); a.setAttribute("class", Y(d)) } } function Xc(a, b) {
            if (b) if (b.nodeType) a[a.length++] =
                b; else { var d = b.length; if ("number" === typeof d && b.window !== b) { if (d) for (var c = 0; c < d; c++)a[a.length++] = b[c] } else a[a.length++] = b }
        } function Zc(a, b) { return Fb(a, "$" + (b || "ngController") + "Controller") } function Fb(a, b, d) { 9 === a.nodeType && (a = a.documentElement); for (b = I(b) ? b : [b]; a;) { for (var c = 0, f = b.length; c < f; c++)if (x(d = F.data(a, b[c]))) return d; a = a.parentNode || 11 === a.nodeType && a.host } } function $c(a) { for (zb(a, !0); a.firstChild;)a.removeChild(a.firstChild) } function Gb(a, b) { b || zb(a); var d = a.parentNode; d && d.removeChild(a) }
    function hg(a, b) { b = b || w; if ("complete" === b.document.readyState) b.setTimeout(a); else F(b).on("load", a) } function ad(a, b) { var d = Hb[b.toLowerCase()]; return d && bd[wa(a)] && d } function ig(a, b) {
        var d = function (c, d) {
            c.isDefaultPrevented = function () { return c.defaultPrevented }; var e = b[d || c.type], g = e ? e.length : 0; if (g) {
                if (z(c.immediatePropagationStopped)) { var h = c.stopImmediatePropagation; c.stopImmediatePropagation = function () { c.immediatePropagationStopped = !0; c.stopPropagation && c.stopPropagation(); h && h.call(c) } } c.isImmediatePropagationStopped =
                    function () { return !0 === c.immediatePropagationStopped }; var k = e.specialHandlerWrapper || jg; 1 < g && (e = ka(e)); for (var l = 0; l < g; l++)c.isImmediatePropagationStopped() || k(a, c, e[l])
            }
        }; d.elem = a; return d
    } function jg(a, b, d) { d.call(a, b) } function kg(a, b, d) { var c = b.relatedTarget; c && (c === a || lg.call(a, c)) || d.call(a, b) } function $f() {
        this.$get = function () {
            return R(S, {
                hasClass: function (a, b) { a.attr && (a = a[0]); return Cb(a, b) }, addClass: function (a, b) { a.attr && (a = a[0]); return Eb(a, b) }, removeClass: function (a, b) {
                    a.attr && (a = a[0]);
                    return Db(a, b)
                }
            })
        }
    } function Aa(a, b) { var d = a && a.$$hashKey; if (d) return "function" === typeof d && (d = a.$$hashKey()), d; d = typeof a; return d = "function" === d || "object" === d && null !== a ? a.$$hashKey = d + ":" + (b || oe)() : d + ":" + a } function Sa(a, b) { if (b) { var d = 0; this.nextUid = function () { return ++d } } q(a, this.put, this) } function cd(a) { a = (Function.prototype.toString.call(a) + " ").replace(mg, ""); return a.match(ng) || a.match(og) } function pg(a) { return (a = cd(a)) ? "function(" + (a[1] || "").replace(/[\s\r\n]+/, " ") + ")" : "fn" } function fb(a, b) {
        function d(a) {
            return function (b,
                c) { if (E(b)) q(b, Bc(a)); else return a(b, c) }
        } function c(a, b) { Ra(a, "service"); if (D(b) || I(b)) b = r.instantiate(b); if (!b.$get) throw Ba("pget", a); return n[a + "Provider"] = b } function f(a, b) { return function () { var c = u.invoke(b, this); if (z(c)) throw Ba("undef", a); return c } } function e(a, b, d) { return c(a, { $get: !1 !== d ? f(a, b) : b }) } function g(a) {
            gb(z(a) || I(a), "modulesToLoad", "not an array"); var b = [], c; q(a, function (a) {
                function d(a) { var b, c; b = 0; for (c = a.length; b < c; b++) { var e = a[b], f = r.get(e[0]); f[e[1]].apply(f, e[2]) } } if (!m.get(a)) {
                    m.put(a,
                        !0); try { C(a) ? (c = Xb(a), b = b.concat(g(c.requires)).concat(c._runBlocks), d(c._invokeQueue), d(c._configBlocks)) : D(a) ? b.push(r.invoke(a)) : I(a) ? b.push(r.invoke(a)) : Qa(a, "module") } catch (e) { throw I(a) && (a = a[a.length - 1]), e.message && e.stack && -1 === e.stack.indexOf(e.message) && (e = e.message + "\n" + e.stack), Ba("modulerr", a, e.stack || e.message || e); }
                }
            }); return b
        } function h(a, c) {
            function d(b, e) {
                if (a.hasOwnProperty(b)) { if (a[b] === k) throw Ba("cdep", b + " <- " + l.join(" <- ")); return a[b] } try {
                    return l.unshift(b), a[b] = k, a[b] =
                        c(b, e), a[b]
                } catch (f) { throw a[b] === k && delete a[b], f; } finally { l.shift() }
            } function e(a, c, f) { var g = []; a = fb.$$annotate(a, b, f); for (var h = 0, k = a.length; h < k; h++) { var l = a[h]; if ("string" !== typeof l) throw Ba("itkn", l); g.push(c && c.hasOwnProperty(l) ? c[l] : d(l, f)) } return g } return {
                invoke: function (a, b, c, d) {
                    "string" === typeof c && (d = c, c = null); c = e(a, c, d); I(a) && (a = a[a.length - 1]); d = 11 >= Ga ? !1 : "function" === typeof a && /^(?:class\b|constructor\()/.test(Function.prototype.toString.call(a) + " "); return d ? (c.unshift(null), new (Function.prototype.bind.apply(a,
                        c))) : a.apply(b, c)
                }, instantiate: function (a, b, c) { var d = I(a) ? a[a.length - 1] : a; a = e(a, b, c); a.unshift(null); return new (Function.prototype.bind.apply(d, a)) }, get: d, annotate: fb.$$annotate, has: function (b) { return n.hasOwnProperty(b + "Provider") || a.hasOwnProperty(b) }
            }
        } b = !0 === b; var k = {}, l = [], m = new Sa([], !0), n = {
            $provide: {
                provider: d(c), factory: d(e), service: d(function (a, b) { return e(a, ["$injector", function (a) { return a.instantiate(b) }]) }), value: d(function (a, b) { return e(a, ga(b), !1) }), constant: d(function (a, b) {
                    Ra(a, "constant");
                    n[a] = b; s[a] = b
                }), decorator: function (a, b) { var c = r.get(a + "Provider"), d = c.$get; c.$get = function () { var a = u.invoke(d, c); return u.invoke(b, null, { $delegate: a }) } }
            }
        }, r = n.$injector = h(n, function (a, b) { $.isString(b) && l.push(b); throw Ba("unpr", l.join(" <- ")); }), s = {}, H = h(s, function (a, b) { var c = r.get(a + "Provider", b); return u.invoke(c.$get, c, void 0, a) }), u = H; n.$injectorProvider = { $get: ga(H) }; var p = g(a), u = H.get("$injector"); u.strictDi = b; q(p, function (a) { a && u.invoke(a) }); return u
    } function qf() {
        var a = !0; this.disableAutoScrolling =
            function () { a = !1 }; this.$get = ["$window", "$location", "$rootScope", function (b, d, c) {
                function f(a) { var b = null; Array.prototype.some.call(a, function (a) { if ("a" === wa(a)) return b = a, !0 }); return b } function e(a) { if (a) { a.scrollIntoView(); var c; c = g.yOffset; D(c) ? c = c() : Tb(c) ? (c = c[0], c = "fixed" !== b.getComputedStyle(c).position ? 0 : c.getBoundingClientRect().bottom) : ba(c) || (c = 0); c && (a = a.getBoundingClientRect().top, b.scrollBy(0, a - c)) } else b.scrollTo(0, 0) } function g(a) {
                    a = C(a) ? a : ba(a) ? a.toString() : d.hash(); var b; a ? (b = h.getElementById(a)) ?
                        e(b) : (b = f(h.getElementsByName(a))) ? e(b) : "top" === a && e(null) : e(null)
                } var h = b.document; a && c.$watch(function () { return d.hash() }, function (a, b) { a === b && "" === a || hg(function () { c.$evalAsync(g) }) }); return g
            }]
    } function lb(a, b) { if (!a && !b) return ""; if (!a) return b; if (!b) return a; I(a) && (a = a.join(" ")); I(b) && (b = b.join(" ")); return a + " " + b } function qg(a) { C(a) && (a = a.split(" ")); var b = W(); q(a, function (a) { a.length && (b[a] = !0) }); return b } function Ca(a) { return E(a) ? a : {} } function rg(a, b, d, c) {
        function f(a) {
            try {
                a.apply(null,
                    va.call(arguments, 1))
            } finally { if (H--, 0 === H) for (; u.length;)try { u.pop()() } catch (b) { d.error(b) } }
        } function e() { M = null; g(); h() } function g() { p = N(); p = z(p) ? null : p; na(p, J) && (p = J); J = p } function h() { if (A !== k.url() || K !== p) A = k.url(), K = p, q(O, function (a) { a(k.url(), p) }) } var k = this, l = a.location, m = a.history, n = a.setTimeout, r = a.clearTimeout, s = {}; k.isMock = !1; var H = 0, u = []; k.$$completeOutstandingRequest = f; k.$$incOutstandingRequestCount = function () { H++ }; k.notifyWhenNoOutstandingRequests = function (a) { 0 === H ? a() : u.push(a) }; var p,
            K, A = l.href, v = b.find("base"), M = null, N = c.history ? function () { try { return m.state } catch (a) { } } : y; g(); K = p; k.url = function (b, d, e) {
                z(e) && (e = null); l !== a.location && (l = a.location); m !== a.history && (m = a.history); if (b) { var f = K === e; if (A === b && (!c.history || f)) return k; var h = A && Ha(A) === Ha(b); A = b; K = e; !c.history || h && f ? (h || (M = b), d ? l.replace(b) : h ? (d = l, e = b.indexOf("#"), e = -1 === e ? "" : b.substr(e), d.hash = e) : l.href = b, l.href !== b && (M = b)) : (m[d ? "replaceState" : "pushState"](e, "", b), g(), K = p); M && (M = b); return k } return M || l.href.replace(/%27/g,
                    "'")
            }; k.state = function () { return p }; var O = [], L = !1, J = null; k.onUrlChange = function (b) { if (!L) { if (c.history) F(a).on("popstate", e); F(a).on("hashchange", e); L = !0 } O.push(b); return b }; k.$$applicationDestroyed = function () { F(a).off("hashchange popstate", e) }; k.$$checkUrlChange = h; k.baseHref = function () { var a = v.attr("href"); return a ? a.replace(/^(https?:)?\/\/[^/]*/, "") : "" }; k.defer = function (a, b) { var c; H++; c = n(function () { delete s[c]; f(a) }, b || 0); s[c] = !0; return c }; k.defer.cancel = function (a) {
                return s[a] ? (delete s[a], r(a),
                    f(y), !0) : !1
            }
    } function xf() { this.$get = ["$window", "$log", "$sniffer", "$document", function (a, b, d, c) { return new rg(a, c, b, d) }] } function yf() {
        this.$get = function () {
            function a(a, c) {
                function f(a) { a !== n && (r ? r === a && (r = a.n) : r = a, e(a.n, a.p), e(a, n), n = a, n.n = null) } function e(a, b) { a !== b && (a && (a.p = b), b && (b.n = a)) } if (a in b) throw G("$cacheFactory")("iid", a); var g = 0, h = R({}, c, { id: a }), k = W(), l = c && c.capacity || Number.MAX_VALUE, m = W(), n = null, r = null; return b[a] = {
                    put: function (a, b) {
                        if (!z(b)) {
                            if (l < Number.MAX_VALUE) {
                                var c = m[a] || (m[a] =
                                    { key: a }); f(c)
                            } a in k || g++; k[a] = b; g > l && this.remove(r.key); return b
                        }
                    }, get: function (a) { if (l < Number.MAX_VALUE) { var b = m[a]; if (!b) return; f(b) } return k[a] }, remove: function (a) { if (l < Number.MAX_VALUE) { var b = m[a]; if (!b) return; b === n && (n = b.p); b === r && (r = b.n); e(b.n, b.p); delete m[a] } a in k && (delete k[a], g--) }, removeAll: function () { k = W(); g = 0; m = W(); n = r = null }, destroy: function () { m = h = k = null; delete b[a] }, info: function () { return R({}, h, { size: g }) }
                }
            } var b = {}; a.info = function () { var a = {}; q(b, function (b, f) { a[f] = b.info() }); return a };
            a.get = function (a) { return b[a] }; return a
        }
    } function Uf() { this.$get = ["$cacheFactory", function (a) { return a("templates") }] } function Mc(a, b) {
        function d(a, b, c) { var d = /^\s*([@&<]|=(\*?))(\??)\s*([\w$]*)\s*$/, e = W(); q(a, function (a, f) { if (a in n) e[f] = n[a]; else { var g = a.match(d); if (!g) throw fa("iscp", b, f, a, c ? "controller bindings definition" : "isolate scope definition"); e[f] = { mode: g[1][0], collection: "*" === g[2], optional: "?" === g[3], attrName: g[4] || f }; g[4] && (n[a] = e[f]) } }); return e } function c(a) {
            var b = a.charAt(0); if (!b ||
                b !== Q(b)) throw fa("baddir", a); if (a !== a.trim()) throw fa("baddir", a);
        } function f(a) { var b = a.require || a.controller && a.name; !I(b) && E(b) && q(b, function (a, c) { var d = a.match(l); a.substring(d[0].length) || (b[c] = d[0] + c) }); return b } var e = {}, g = /^\s*directive:\s*([\w-]+)\s+(.*)$/, h = /(([\w-]+)(?::([^;]+))?;?)/, k = se("ngSrc,ngSrcset,src,srcset"), l = /^(?:(\^\^?)?(\?)?(\^\^?)?)?/, m = /^(on[a-z]+|formaction)$/, n = W(); this.directive = function A(b, d) {
            gb(b, "name"); Ra(b, "directive"); C(b) ? (c(b), gb(d, "directiveFactory"), e.hasOwnProperty(b) ||
                (e[b] = [], a.factory(b + "Directive", ["$injector", "$exceptionHandler", function (a, c) { var d = []; q(e[b], function (e, g) { try { var h = a.invoke(e); D(h) ? h = { compile: ga(h) } : !h.compile && h.link && (h.compile = ga(h.link)); h.priority = h.priority || 0; h.index = g; h.name = h.name || b; h.require = f(h); var k = h, l = h.restrict; if (l && (!C(l) || !/[EACM]/.test(l))) throw fa("badrestrict", l, b); k.restrict = l || "EA"; h.$$moduleName = e.$$moduleName; d.push(h) } catch (m) { c(m) } }); return d }])), e[b].push(d)) : q(b, Bc(A)); return this
        }; this.component = function (a, b) {
            function c(a) {
                function e(b) {
                    return D(b) ||
                        I(b) ? function (c, d) { return a.invoke(b, this, { $element: c, $attrs: d }) } : b
                } var f = b.template || b.templateUrl ? b.template : "", g = { controller: d, controllerAs: sg(b.controller) || b.controllerAs || "$ctrl", template: e(f), templateUrl: e(b.templateUrl), transclude: b.transclude, scope: {}, bindToController: b.bindings || {}, restrict: "E", require: b.require }; q(b, function (a, b) { "$" === b.charAt(0) && (g[b] = a) }); return g
            } var d = b.controller || function () { }; q(b, function (a, b) { "$" === b.charAt(0) && (c[b] = a, D(d) && (d[b] = a)) }); c.$inject = ["$injector"];
            return this.directive(a, c)
        }; this.aHrefSanitizationWhitelist = function (a) { return x(a) ? (b.aHrefSanitizationWhitelist(a), this) : b.aHrefSanitizationWhitelist() }; this.imgSrcSanitizationWhitelist = function (a) { return x(a) ? (b.imgSrcSanitizationWhitelist(a), this) : b.imgSrcSanitizationWhitelist() }; var r = !0; this.debugInfoEnabled = function (a) { return x(a) ? (r = a, this) : r }; var s = !0; this.preAssignBindingsEnabled = function (a) { return x(a) ? (s = a, this) : s }; var H = 10; this.onChangesTtl = function (a) {
            return arguments.length ? (H = a, this) :
                H
        }; var u = !0; this.commentDirectivesEnabled = function (a) { return arguments.length ? (u = a, this) : u }; var p = !0; this.cssClassDirectivesEnabled = function (a) { return arguments.length ? (p = a, this) : p }; this.$get = ["$injector", "$interpolate", "$exceptionHandler", "$templateRequest", "$parse", "$controller", "$rootScope", "$sce", "$animate", "$$sanitizeUri", function (a, b, c, f, n, L, J, B, U, T) {
            function P() {
                try {
                    if (!--xa) throw da = void 0, fa("infchng", H); J.$apply(function () {
                        for (var a = [], b = 0, c = da.length; b < c; ++b)try { da[b]() } catch (d) { a.push(d) } da =
                            void 0; if (a.length) throw a;
                    })
                } finally { xa++ }
            } function t(a, b) { if (b) { var c = Object.keys(b), d, e, f; d = 0; for (e = c.length; d < e; d++)f = c[d], this[f] = b[f] } else this.$attr = {}; this.$$element = a } function pa(a, b, c) { ta.innerHTML = "<span " + b + ">"; b = ta.firstChild.attributes; var d = b[0]; b.removeNamedItem(d.name); d.value = c; a.attributes.setNamedItem(d) } function Ja(a, b) { try { a.addClass(b) } catch (c) { } } function ca(a, b, c, d, e) {
                a instanceof F || (a = F(a)); for (var f = /\S+/, g = 0, h = a.length; g < h; g++) {
                    var k = a[g]; k.nodeType === La && k.nodeValue.match(f) &&
                        Wc(k, a[g] = w.document.createElement("span"))
                } var l = Ma(a, b, a, c, d, e); ca.$$addScopeClass(a); var m = null; return function (b, c, d) {
                    gb(b, "scope"); e && e.needsNewScope && (b = b.$parent.$new()); d = d || {}; var f = d.parentBoundTranscludeFn, g = d.transcludeControllers; d = d.futureParentElement; f && f.$$boundTransclude && (f = f.$$boundTransclude); m || (m = (d = d && d[0]) ? "foreignobject" !== wa(d) && ma.call(d).match(/SVG/) ? "svg" : "html" : "html"); d = "html" !== m ? F(ga(m, F("<div>").append(a).html())) : c ? Pa.clone.call(a) : a; if (g) for (var h in g) d.data("$" +
                        h + "Controller", g[h].instance); ca.$$addScopeInfo(d, b); c && c(d, b); l && l(b, d, d, f); return d
                }
            } function Ma(a, b, c, d, e, f) {
                function g(a, c, d, e) { var f, k, l, m, n, s, A; if (p) for (A = Array(c.length), m = 0; m < h.length; m += 3)f = h[m], A[f] = c[f]; else A = c; m = 0; for (n = h.length; m < n;)k = A[h[m++]], c = h[m++], f = h[m++], c ? (c.scope ? (l = a.$new(), ca.$$addScopeInfo(F(k), l)) : l = a, s = c.transcludeOnThisElement ? G(a, c.transclude, e) : !c.templateOnThisElement && e ? e : !e && b ? G(a, b) : null, c(f, l, k, d, s)) : f && f(a, k.childNodes, void 0, e) } for (var h = [], k, l, m, n, p, s = 0; s < a.length; s++) {
                    k =
                    new t; l = dc(a[s], [], k, 0 === s ? d : void 0, e); (f = l.length ? S(l, a[s], k, b, c, null, [], [], f) : null) && f.scope && ca.$$addScopeClass(k.$$element); k = f && f.terminal || !(m = a[s].childNodes) || !m.length ? null : Ma(m, f ? (f.transcludeOnThisElement || !f.templateOnThisElement) && f.transclude : b); if (f || k) h.push(s, f, k), n = !0, p = p || f; f = null
                } return n ? g : null
            } function G(a, b, c) {
                function d(e, f, g, h, k) { e || (e = a.$new(!1, k), e.$$transcluded = !0); return b(e, f, { parentBoundTranscludeFn: c, transcludeControllers: g, futureParentElement: h }) } var e = d.$$slots = W(),
                    f; for (f in b.$$slots) e[f] = b.$$slots[f] ? G(a, b.$$slots[f], c) : null; return d
            } function dc(a, b, c, d, e) {
                var f = c.$attr, g; switch (a.nodeType) {
                    case 1: g = wa(a); V(b, Da(g), "E", d, e); for (var k, l, m, n, p = a.attributes, s = 0, A = p && p.length; s < A; s++) {
                        var r = !1, u = !1; k = p[s]; l = k.name; m = Y(k.value); k = Da(l); (n = Ha.test(k)) && (l = l.replace(dd, "").substr(8).replace(/_(.)/g, function (a, b) { return b.toUpperCase() })); (k = k.match(Ia)) && Z(k[1]) && (r = l, u = l.substr(0, l.length - 5) + "end", l = l.substr(0, l.length - 6)); k = Da(l.toLowerCase()); f[k] = l; if (n || !c.hasOwnProperty(k)) c[k] =
                            m, ad(a, k) && (c[k] = !0); ra(a, b, m, k, n); V(b, k, "A", d, e, r, u)
                    } "input" === g && "hidden" === a.getAttribute("type") && a.setAttribute("autocomplete", "off"); if (!Fa) break; f = a.className; E(f) && (f = f.animVal); if (C(f) && "" !== f) for (; a = h.exec(f);)k = Da(a[2]), V(b, k, "C", d, e) && (c[k] = Y(a[3])), f = f.substr(a.index + a[0].length); break; case La: if (11 === Ga) for (; a.parentNode && a.nextSibling && a.nextSibling.nodeType === La;)a.nodeValue += a.nextSibling.nodeValue, a.parentNode.removeChild(a.nextSibling); ka(b, a.nodeValue); break; case 8: if (!Ea) break;
                        Ta(a, b, c, d, e)
                }b.sort(ja); return b
            } function Ta(a, b, c, d, e) { try { var f = g.exec(a.nodeValue); if (f) { var h = Da(f[1]); V(b, h, "M", d, e) && (c[h] = Y(f[2])) } } catch (k) { } } function ed(a, b, c) { var d = [], e = 0; if (b && a.hasAttribute && a.hasAttribute(b)) { do { if (!a) throw fa("uterdir", b, c); 1 === a.nodeType && (a.hasAttribute(b) && e++, a.hasAttribute(c) && e--); d.push(a); a = a.nextSibling } while (0 < e) } else d.push(a); return F(d) } function fd(a, b, c) { return function (d, e, f, g, h) { e = ed(e[0], b, c); return a(d, e, f, g, h) } } function ec(a, b, c, d, e, f) {
                var g; return a ?
                    ca(b, c, d, e, f) : function () { g || (g = ca(b, c, d, e, f), b = c = f = null); return g.apply(this, arguments) }
            } function S(a, b, d, e, f, g, h, k, l) {
                function m(a, b, c, d) { if (a) { c && (a = fd(a, c, d)); a.require = v.require; a.directiveName = T; if (u === v || v.$$isolateScope) a = qa(a, { isolateScope: !0 }); h.push(a) } if (b) { c && (b = fd(b, c, d)); b.require = v.require; b.directiveName = T; if (u === v || v.$$isolateScope) b = qa(b, { isolateScope: !0 }); k.push(b) } } function n(a, e, f, g, l) {
                    function m(a, b, c, d) {
                        var e; ab(a) || (d = c, c = b, b = a, a = void 0); H && (e = J); c || (c = H ? P.parent() : P); if (d) {
                            var f =
                                l.$$slots[d]; if (f) return f(a, b, e, c, pa); if (z(f)) throw fa("noslot", d, ya(P));
                        } else return l(a, b, e, c, pa)
                    } var p, v, B, L, U, J, T, P; b === f ? (g = d, P = d.$$element) : (P = F(f), g = new t(P, d)); U = e; u ? L = e.$new(!0) : A && (U = e.$parent); l && (T = m, T.$$boundTransclude = l, T.isSlotFilled = function (a) { return !!l.$$slots[a] }); r && (J = ba(P, g, T, r, L, e, u)); u && (ca.$$addScopeInfo(P, L, !0, !(O && (O === u || O === u.$$originalDirective))), ca.$$addScopeClass(P, !0), L.$$isolateBindings = u.$$isolateBindings, v = la(e, g, L, L.$$isolateBindings, u), v.removeWatches && L.$on("$destroy",
                        v.removeWatches)); for (p in J) { v = r[p]; B = J[p]; var N = v.$$bindings.bindToController; if (s) { B.bindingInfo = N ? la(U, g, B.instance, N, v) : {}; var bc = B(); bc !== B.instance && (B.instance = bc, P.data("$" + v.name + "Controller", bc), B.bindingInfo.removeWatches && B.bindingInfo.removeWatches(), B.bindingInfo = la(U, g, B.instance, N, v)) } else B.instance = B(), P.data("$" + v.name + "Controller", B.instance), B.bindingInfo = la(U, g, B.instance, N, v) } q(r, function (a, b) { var c = a.require; a.bindToController && !I(c) && E(c) && R(J[b].instance, X(b, c, P, J)) }); q(J,
                            function (a) { var b = a.instance; if (D(b.$onChanges)) try { b.$onChanges(a.bindingInfo.initialChanges) } catch (d) { c(d) } if (D(b.$onInit)) try { b.$onInit() } catch (e) { c(e) } D(b.$doCheck) && (U.$watch(function () { b.$doCheck() }), b.$doCheck()); D(b.$onDestroy) && U.$on("$destroy", function () { b.$onDestroy() }) }); p = 0; for (v = h.length; p < v; p++)B = h[p], sa(B, B.isolateScope ? L : e, P, g, B.require && X(B.directiveName, B.require, P, J), T); var pa = e; u && (u.template || null === u.templateUrl) && (pa = L); a && a(pa, f.childNodes, void 0, l); for (p = k.length - 1; 0 <= p; p--)B =
                                k[p], sa(B, B.isolateScope ? L : e, P, g, B.require && X(B.directiveName, B.require, P, J), T); q(J, function (a) { a = a.instance; D(a.$postLink) && a.$postLink() })
                } l = l || {}; for (var p = -Number.MAX_VALUE, A = l.newScopeDirective, r = l.controllerDirectives, u = l.newIsolateScopeDirective, O = l.templateDirective, L = l.nonTlbTranscludeDirective, U = !1, J = !1, H = l.hasElementTranscludeDirective, B = d.$$element = F(b), v, T, P, N = e, pa, x = !1, Ja = !1, w, y = 0, C = a.length; y < C; y++) {
                    v = a[y]; var Ta = v.$$start, Ma = v.$$end; Ta && (B = ed(b, Ta, Ma)); P = void 0; if (p > v.priority) break;
                    if (w = v.scope) v.templateUrl || (E(w) ? ($("new/isolated scope", u || A, v, B), u = v) : $("new/isolated scope", u, v, B)), A = A || v; T = v.name; if (!x && (v.replace && (v.templateUrl || v.template) || v.transclude && !v.$$tlb)) { for (w = y + 1; x = a[w++];)if (x.transclude && !x.$$tlb || x.replace && (x.templateUrl || x.template)) { Ja = !0; break } x = !0 } !v.templateUrl && v.controller && (r = r || W(), $("'" + T + "' controller", r[T], v, B), r[T] = v); if (w = v.transclude) if (U = !0, v.$$tlb || ($("transclusion", L, v, B), L = v), "element" === w) H = !0, p = v.priority, P = B, B = d.$$element = F(ca.$$createComment(T,
                        d[T])), b = B[0], ia(f, va.call(P, 0), b), P[0].$$parentNode = P[0].parentNode, N = ec(Ja, P, e, p, g && g.name, { nonTlbTranscludeDirective: L }); else {
                            var G = W(); P = F($b(b)).contents(); if (E(w)) { P = []; var Q = W(), cc = W(); q(w, function (a, b) { var c = "?" === a.charAt(0); a = c ? a.substring(1) : a; Q[a] = b; G[b] = null; cc[b] = c }); q(B.contents(), function (a) { var b = Q[Da(wa(a))]; b ? (cc[b] = !0, G[b] = G[b] || [], G[b].push(a)) : P.push(a) }); q(cc, function (a, b) { if (!a) throw fa("reqslot", b); }); for (var V in G) G[V] && (G[V] = ec(Ja, G[V], e)) } B.empty(); N = ec(Ja, P, e, void 0,
                                void 0, { needsNewScope: v.$$isolateScope || v.$$newScope }); N.$$slots = G
                    } if (v.template) if (J = !0, $("template", O, v, B), O = v, w = D(v.template) ? v.template(B, d) : v.template, w = Ca(w), v.replace) { g = v; P = Yb.test(w) ? gd(ga(v.templateNamespace, Y(w))) : []; b = P[0]; if (1 !== P.length || 1 !== b.nodeType) throw fa("tplrt", T, ""); ia(f, B, b); C = { $attr: {} }; w = dc(b, [], C); var tg = a.splice(y + 1, a.length - (y + 1)); (u || A) && aa(w, u, A); a = a.concat(w).concat(tg); ea(d, C); C = a.length } else B.html(w); if (v.templateUrl) J = !0, $("template", O, v, B), O = v, v.replace && (g = v),
                        n = ha(a.splice(y, a.length - y), B, d, f, U && N, h, k, { controllerDirectives: r, newScopeDirective: A !== v && A, newIsolateScopeDirective: u, templateDirective: O, nonTlbTranscludeDirective: L }), C = a.length; else if (v.compile) try { pa = v.compile(B, d, N); var Z = v.$$originalDirective || v; D(pa) ? m(null, db(Z, pa), Ta, Ma) : pa && m(db(Z, pa.pre), db(Z, pa.post), Ta, Ma) } catch (da) { c(da, ya(B)) } v.terminal && (n.terminal = !0, p = Math.max(p, v.priority))
                } n.scope = A && !0 === A.scope; n.transcludeOnThisElement = U; n.templateOnThisElement = J; n.transclude = N; l.hasElementTranscludeDirective =
                    H; return n
            } function X(a, b, c, d) { var e; if (C(b)) { var f = b.match(l); b = b.substring(f[0].length); var g = f[1] || f[3], f = "?" === f[2]; "^^" === g ? c = c.parent() : e = (e = d && d[b]) && e.instance; if (!e) { var h = "$" + b + "Controller"; e = g ? c.inheritedData(h) : c.data(h) } if (!e && !f) throw fa("ctreq", b, a); } else if (I(b)) for (e = [], g = 0, f = b.length; g < f; g++)e[g] = X(a, b[g], c, d); else E(b) && (e = {}, q(b, function (b, f) { e[f] = X(a, b, c, d) })); return e || null } function ba(a, b, c, d, e, f, g) {
                var h = W(), k; for (k in d) {
                    var l = d[k], m = {
                        $scope: l === g || l.$$isolateScope ? e : f, $element: a,
                        $attrs: b, $transclude: c
                    }, n = l.controller; "@" === n && (n = b[l.name]); m = L(n, m, !0, l.controllerAs); h[l.name] = m; a.data("$" + l.name + "Controller", m.instance)
                } return h
            } function aa(a, b, c) { for (var d = 0, e = a.length; d < e; d++)a[d] = Ub(a[d], { $$isolateScope: b, $$newScope: c }) } function V(b, c, f, g, h, k, l) {
                if (c === h) return null; var m = null; if (e.hasOwnProperty(c)) {
                    h = a.get(c + "Directive"); for (var n = 0, p = h.length; n < p; n++)if (c = h[n], (z(g) || g > c.priority) && -1 !== c.restrict.indexOf(f)) {
                        k && (c = Ub(c, { $$start: k, $$end: l })); if (!c.$$bindings) {
                            var s =
                                m = c, r = c.name, v = { isolateScope: null, bindToController: null }; E(s.scope) && (!0 === s.bindToController ? (v.bindToController = d(s.scope, r, !0), v.isolateScope = {}) : v.isolateScope = d(s.scope, r, !1)); E(s.bindToController) && (v.bindToController = d(s.bindToController, r, !0)); if (v.bindToController && !s.controller) throw fa("noctrl", r); m = m.$$bindings = v; E(m.isolateScope) && (c.$$isolateBindings = m.isolateScope)
                        } b.push(c); m = c
                    }
                } return m
            } function Z(b) {
                if (e.hasOwnProperty(b)) for (var c = a.get(b + "Directive"), d = 0, f = c.length; d < f; d++)if (b =
                    c[d], b.multiElement) return !0; return !1
            } function ea(a, b) { var c = b.$attr, d = a.$attr; q(a, function (d, e) { "$" !== e.charAt(0) && (b[e] && b[e] !== d && (d += ("style" === e ? ";" : " ") + b[e]), a.$set(e, d, !0, c[e])) }); q(b, function (b, e) { a.hasOwnProperty(e) || "$" === e.charAt(0) || (a[e] = b, "class" !== e && "style" !== e && (d[e] = c[e])) }) } function ha(a, b, c, d, e, g, h, k) {
                var l = [], m, n, p = b[0], s = a.shift(), A = Ub(s, { templateUrl: null, transclude: null, replace: null, $$originalDirective: s }), r = D(s.templateUrl) ? s.templateUrl(b, c) : s.templateUrl, v = s.templateNamespace;
                b.empty(); f(r).then(function (f) {
                    var u, B; f = Ca(f); if (s.replace) { f = Yb.test(f) ? gd(ga(v, Y(f))) : []; u = f[0]; if (1 !== f.length || 1 !== u.nodeType) throw fa("tplrt", s.name, r); f = { $attr: {} }; ia(d, b, u); var O = dc(u, [], f); E(s.scope) && aa(O, !0); a = O.concat(a); ea(c, f) } else u = p, b.html(f); a.unshift(A); m = S(a, u, c, e, b, s, g, h, k); q(d, function (a, c) { a === u && (d[c] = b[0]) }); for (n = Ma(b[0].childNodes, e); l.length;) {
                        f = l.shift(); B = l.shift(); var L = l.shift(), U = l.shift(), O = b[0]; if (!f.$$destroyed) {
                            if (B !== p) {
                                var J = B.className; k.hasElementTranscludeDirective &&
                                    s.replace || (O = $b(u)); ia(L, F(B), O); Ja(F(O), J)
                            } B = m.transcludeOnThisElement ? G(f, m.transclude, U) : U; m(n, f, O, d, B)
                        }
                    } l = null
                }); return function (a, b, c, d, e) { a = e; b.$$destroyed || (l ? l.push(b, c, d, a) : (m.transcludeOnThisElement && (a = G(b, m.transclude, e)), m(n, b, c, d, a))) }
            } function ja(a, b) { var c = b.priority - a.priority; return 0 !== c ? c : a.name !== b.name ? a.name < b.name ? -1 : 1 : a.index - b.index } function $(a, b, c, d) {
                function e(a) { return a ? " (module: " + a + ")" : "" } if (b) throw fa("multidir", b.name, e(b.$$moduleName), c.name, e(c.$$moduleName),
                    a, ya(d));
            } function ka(a, c) { var d = b(c, !0); d && a.push({ priority: 0, compile: function (a) { a = a.parent(); var b = !!a.length; b && ca.$$addBindingClass(a); return function (a, c) { var e = c.parent(); b || ca.$$addBindingClass(e); ca.$$addBindingInfo(e, d.expressions); a.$watch(d, function (a) { c[0].nodeValue = a }) } } }) } function ga(a, b) { a = Q(a || "html"); switch (a) { case "svg": case "math": var c = w.document.createElement("div"); c.innerHTML = "<" + a + ">" + b + "</" + a + ">"; return c.childNodes[0].childNodes; default: return b } } function oa(a, b) {
                if ("srcdoc" ===
                    b) return B.HTML; var c = wa(a); if ("src" === b || "ngSrc" === b) { if (-1 === ["img", "video", "audio", "source", "track"].indexOf(c)) return B.RESOURCE_URL } else if ("xlinkHref" === b || "form" === c && "action" === b) return B.RESOURCE_URL
            } function ra(a, c, d, e, f) {
                var g = oa(a, e), h = k[e] || f, l = b(d, !f, g, h); if (l) {
                    if ("multiple" === e && "select" === wa(a)) throw fa("selmulti", ya(a)); c.push({
                        priority: 100, compile: function () {
                            return {
                                pre: function (a, c, f) {
                                    c = f.$$observers || (f.$$observers = W()); if (m.test(e)) throw fa("nodomevents"); var k = f[e]; k !== d && (l =
                                        k && b(k, !0, g, h), d = k); l && (f[e] = l(a), (c[e] || (c[e] = [])).$$inter = !0, (f.$$observers && f.$$observers[e].$$scope || a).$watch(l, function (a, b) { "class" === e && a !== b ? f.$updateClass(a, b) : f.$set(e, a) }))
                                }
                            }
                        }
                    })
                }
            } function ia(a, b, c) {
                var d = b[0], e = b.length, f = d.parentNode, g, h; if (a) for (g = 0, h = a.length; g < h; g++)if (a[g] === d) { a[g++] = c; h = g + e - 1; for (var k = a.length; g < k; g++, h++)h < k ? a[g] = a[h] : delete a[g]; a.length -= e - 1; a.context === d && (a.context = c); break } f && f.replaceChild(c, d); a = w.document.createDocumentFragment(); for (g = 0; g < e; g++)a.appendChild(b[g]);
                F.hasData(d) && (F.data(c, F.data(d)), F(d).off("$destroy")); F.cleanData(a.querySelectorAll("*")); for (g = 1; g < e; g++)delete b[g]; b[0] = c; b.length = 1
            } function qa(a, b) { return R(function () { return a.apply(null, arguments) }, a, b) } function sa(a, b, d, e, f, g) { try { a(b, d, e, f, g) } catch (h) { c(h, ya(d)) } } function la(a, c, d, e, f) {
                function g(b, c, e) { !D(d.$onChanges) || c === e || c !== c && e !== e || (da || (a.$$postDigest(P), da = []), m || (m = {}, da.push(h)), m[b] && (e = m[b].previousValue), m[b] = new Ib(e, c)) } function h() { d.$onChanges(m); m = void 0 } var k = [],
                    l = {}, m; q(e, function (e, h) {
                        var m = e.attrName, p = e.optional, s, A, r, u; switch (e.mode) {
                            case "@": p || ua.call(c, m) || (d[h] = c[m] = void 0); p = c.$observe(m, function (a) { if (C(a) || Ka(a)) g(h, a, d[h]), d[h] = a }); c.$$observers[m].$$scope = a; s = c[m]; C(s) ? d[h] = b(s)(a) : Ka(s) && (d[h] = s); l[h] = new Ib(fc, d[h]); k.push(p); break; case "=": if (!ua.call(c, m)) { if (p) break; c[m] = void 0 } if (p && !c[m]) break; A = n(c[m]); u = A.literal ? na : function (a, b) { return a === b || a !== a && b !== b }; r = A.assign || function () { s = d[h] = A(a); throw fa("nonassign", c[m], m, f.name); }; s =
                                d[h] = A(a); p = function (b) { u(b, d[h]) || (u(b, s) ? r(a, b = d[h]) : d[h] = b); return s = b }; p.$stateful = !0; p = e.collection ? a.$watchCollection(c[m], p) : a.$watch(n(c[m], p), null, A.literal); k.push(p); break; case "<": if (!ua.call(c, m)) { if (p) break; c[m] = void 0 } if (p && !c[m]) break; A = n(c[m]); var B = A.literal, L = d[h] = A(a); l[h] = new Ib(fc, d[h]); p = a.$watch(A, function (a, b) { if (b === a) { if (b === L || B && na(b, L)) return; b = L } g(h, a, b); d[h] = a }, B); k.push(p); break; case "&": A = c.hasOwnProperty(m) ? n(c[m]) : y; if (A === y && p) break; d[h] = function (b) {
                                    return A(a,
                                        b)
                                }
                        }
                    }); return { initialChanges: l, removeWatches: k.length && function () { for (var a = 0, b = k.length; a < b; ++a)k[a]() } }
            } var za = /^\w/, ta = w.document.createElement("div"), Ea = u, Fa = p, xa = H, da; t.prototype = {
                $normalize: Da, $addClass: function (a) { a && 0 < a.length && U.addClass(this.$$element, a) }, $removeClass: function (a) { a && 0 < a.length && U.removeClass(this.$$element, a) }, $updateClass: function (a, b) { var c = hd(a, b); c && c.length && U.addClass(this.$$element, c); (c = hd(b, a)) && c.length && U.removeClass(this.$$element, c) }, $set: function (a, b, d, e) {
                    var f =
                        ad(this.$$element[0], a), g = id[a], h = a; f ? (this.$$element.prop(a, b), e = f) : g && (this[g] = b, h = g); this[a] = b; e ? this.$attr[a] = e : (e = this.$attr[a]) || (this.$attr[a] = e = Jc(a, "-")); f = wa(this.$$element); if ("a" === f && ("href" === a || "xlinkHref" === a) || "img" === f && "src" === a) this[a] = b = T(b, "src" === a); else if ("img" === f && "srcset" === a && x(b)) {
                            for (var f = "", g = Y(b), k = /(\s+\d+x\s*,|\s+\d+w\s*,|\s+,|,\s+)/, k = /\s/.test(g) ? k : /(,)/, g = g.split(k), k = Math.floor(g.length / 2), l = 0; l < k; l++)var m = 2 * l, f = f + T(Y(g[m]), !0), f = f + (" " + Y(g[m + 1])); g = Y(g[2 *
                                l]).split(/\s/); f += T(Y(g[0]), !0); 2 === g.length && (f += " " + Y(g[1])); this[a] = b = f
                        } !1 !== d && (null === b || z(b) ? this.$$element.removeAttr(e) : za.test(e) ? this.$$element.attr(e, b) : pa(this.$$element[0], e, b)); (a = this.$$observers) && q(a[h], function (a) { try { a(b) } catch (d) { c(d) } })
                }, $observe: function (a, b) { var c = this, d = c.$$observers || (c.$$observers = W()), e = d[a] || (d[a] = []); e.push(b); J.$evalAsync(function () { e.$$inter || !c.hasOwnProperty(a) || z(c[a]) || b(c[a]) }); return function () { bb(e, b) } }
            }; var Aa = b.startSymbol(), Ba = b.endSymbol(),
                Ca = "{{" === Aa && "}}" === Ba ? $a : function (a) { return a.replace(/\{\{/g, Aa).replace(/}}/g, Ba) }, Ha = /^ngAttr[A-Z]/, Ia = /^(.+)Start$/; ca.$$addBindingInfo = r ? function (a, b) { var c = a.data("$binding") || []; I(b) ? c = c.concat(b) : c.push(b); a.data("$binding", c) } : y; ca.$$addBindingClass = r ? function (a) { Ja(a, "ng-binding") } : y; ca.$$addScopeInfo = r ? function (a, b, c, d) { a.data(c ? d ? "$isolateScopeNoTemplate" : "$isolateScope" : "$scope", b) } : y; ca.$$addScopeClass = r ? function (a, b) { Ja(a, b ? "ng-isolate-scope" : "ng-scope") } : y; ca.$$createComment = function (a,
                    b) { var c = ""; r && (c = " " + (a || "") + ": ", b && (c += b + " ")); return w.document.createComment(c) }; return ca
        }]
    } function Ib(a, b) { this.previousValue = a; this.currentValue = b } function Da(a) { return hb(a.replace(dd, "")) } function hd(a, b) { var d = "", c = a.split(/\s+/), f = b.split(/\s+/), e = 0; a: for (; e < c.length; e++) { for (var g = c[e], h = 0; h < f.length; h++)if (g === f[h]) continue a; d += (0 < d.length ? " " : "") + g } return d } function gd(a) {
        a = F(a); var b = a.length; if (1 >= b) return a; for (; b--;) {
            var d = a[b]; (8 === d.nodeType || d.nodeType === La && "" === d.nodeValue.trim()) &&
                ug.call(a, b, 1)
        } return a
    } function sg(a, b) { if (b && C(b)) return b; if (C(a)) { var d = jd.exec(a); if (d) return d[3] } } function zf() {
        var a = {}, b = !1; this.has = function (b) { return a.hasOwnProperty(b) }; this.register = function (b, c) { Ra(b, "controller"); E(b) ? R(a, b) : a[b] = c }; this.allowGlobals = function () { b = !0 }; this.$get = ["$injector", "$window", function (d, c) {
            function f(a, b, c, d) { if (!a || !E(a.$scope)) throw G("$controller")("noscp", d, b); a.$scope[b] = c } return function (e, g, h, k) {
                var l, m, n; h = !0 === h; k && C(k) && (n = k); if (C(e)) {
                    k = e.match(jd);
                    if (!k) throw kd("ctrlfmt", e); m = k[1]; n = n || k[3]; e = a.hasOwnProperty(m) ? a[m] : Lc(g.$scope, m, !0) || (b ? Lc(c, m, !0) : void 0); if (!e) throw kd("ctrlreg", m); Qa(e, m, !0)
                } if (h) return h = (I(e) ? e[e.length - 1] : e).prototype, l = Object.create(h || null), n && f(g, n, l, m || e.name), R(function () { var a = d.invoke(e, l, g, m); a !== l && (E(a) || D(a)) && (l = a, n && f(g, n, l, m || e.name)); return l }, { instance: l, identifier: n }); l = d.instantiate(e, g, m); n && f(g, n, l, m || e.name); return l
            }
        }]
    } function Af() { this.$get = ["$window", function (a) { return F(a.document) }] } function Bf() {
        this.$get =
        ["$log", function (a) { return function (b, d) { a.error.apply(a, arguments) } }]
    } function gc(a) { return E(a) ? ja(a) ? a.toISOString() : eb(a) : a } function Gf() { this.$get = function () { return function (a) { if (!a) return ""; var b = []; Ac(a, function (a, c) { null === a || z(a) || (I(a) ? q(a, function (a) { b.push(oa(c) + "=" + oa(gc(a))) }) : b.push(oa(c) + "=" + oa(gc(a)))) }); return b.join("&") } } } function Hf() {
        this.$get = function () {
            return function (a) {
                function b(a, f, e) {
                    null === a || z(a) || (I(a) ? q(a, function (a, c) { b(a, f + "[" + (E(a) ? c : "") + "]") }) : E(a) && !ja(a) ? Ac(a,
                        function (a, c) { b(a, f + (e ? "" : "[") + c + (e ? "" : "]")) }) : d.push(oa(f) + "=" + oa(gc(a))))
                } if (!a) return ""; var d = []; b(a, "", !0); return d.join("&")
            }
        }
    } function hc(a, b) { if (C(a)) { var d = a.replace(vg, "").trim(); if (d) { var c = b("Content-Type"); (c = c && 0 === c.indexOf(ld)) || (c = (c = d.match(wg)) && xg[c[0]].test(d)); c && (a = Ec(d)) } } return a } function md(a) {
        var b = W(), d; C(a) ? q(a.split("\n"), function (a) { d = a.indexOf(":"); var f = Q(Y(a.substr(0, d))); a = Y(a.substr(d + 1)); f && (b[f] = b[f] ? b[f] + ", " + a : a) }) : E(a) && q(a, function (a, d) {
            var e = Q(d), g = Y(a); e &&
                (b[e] = b[e] ? b[e] + ", " + g : g)
        }); return b
    } function nd(a) { var b; return function (d) { b || (b = md(a)); return d ? (d = b[Q(d)], void 0 === d && (d = null), d) : b } } function od(a, b, d, c) { if (D(c)) return c(a, b, d); q(c, function (c) { a = c(a, b, d) }); return a } function Ff() {
        var a = this.defaults = {
            transformResponse: [hc], transformRequest: [function (a) { return E(a) && "[object File]" !== ma.call(a) && "[object Blob]" !== ma.call(a) && "[object FormData]" !== ma.call(a) ? eb(a) : a }], headers: {
                common: { Accept: "application/json, text/plain, */*" }, post: ka(ic), put: ka(ic),
                patch: ka(ic)
            }, xsrfCookieName: "XSRF-TOKEN", xsrfHeaderName: "X-XSRF-TOKEN", paramSerializer: "$httpParamSerializer"
        }, b = !1; this.useApplyAsync = function (a) { return x(a) ? (b = !!a, this) : b }; var d = !0; this.useLegacyPromiseExtensions = function (a) { return x(a) ? (d = !!a, this) : d }; var c = this.interceptors = []; this.$get = ["$httpBackend", "$$cookieReader", "$cacheFactory", "$rootScope", "$q", "$injector", function (f, e, g, h, k, l) {
            function m(b) {
                function c(a, b) {
                    for (var d = 0, e = b.length; d < e;) { var f = b[d++], g = b[d++]; a = a.then(f, g) } b.length = 0;
                    return a
                } function e(a, b) { var c, d = {}; q(a, function (a, e) { D(a) ? (c = a(b), null != c && (d[e] = c)) : d[e] = a }); return d } function f(a) { var b = R({}, a); b.data = od(a.data, a.headers, a.status, g.transformResponse); a = a.status; return 200 <= a && 300 > a ? b : k.reject(b) } if (!E(b)) throw G("$http")("badreq", b); if (!C(b.url)) throw G("$http")("badreq", b.url); var g = R({ method: "get", transformRequest: a.transformRequest, transformResponse: a.transformResponse, paramSerializer: a.paramSerializer }, b); g.headers = function (b) {
                    var c = a.headers, d = R({}, b.headers),
                    f, g, h, c = R({}, c.common, c[Q(b.method)]); a: for (f in c) { g = Q(f); for (h in d) if (Q(h) === g) continue a; d[f] = c[f] } return e(d, ka(b))
                }(b); g.method = xb(g.method); g.paramSerializer = C(g.paramSerializer) ? l.get(g.paramSerializer) : g.paramSerializer; var h = [], m = [], s = k.when(g); q(H, function (a) { (a.request || a.requestError) && h.unshift(a.request, a.requestError); (a.response || a.responseError) && m.push(a.response, a.responseError) }); s = c(s, h); s = s.then(function (b) {
                    var c = b.headers, d = od(b.data, nd(c), void 0, b.transformRequest); z(d) &&
                        q(c, function (a, b) { "content-type" === Q(b) && delete c[b] }); z(b.withCredentials) && !z(a.withCredentials) && (b.withCredentials = a.withCredentials); return n(b, d).then(f, f)
                }); s = c(s, m); d ? (s.success = function (a) { Qa(a, "fn"); s.then(function (b) { a(b.data, b.status, b.headers, g) }); return s }, s.error = function (a) { Qa(a, "fn"); s.then(null, function (b) { a(b.data, b.status, b.headers, g) }); return s }) : (s.success = pd("success"), s.error = pd("error")); return s
            } function n(c, d) {
                function g(a) {
                    if (a) {
                        var c = {}; q(a, function (a, d) {
                            c[d] = function (c) {
                                function d() { a(c) }
                                b ? h.$applyAsync(d) : h.$$phase ? d() : h.$apply(d)
                            }
                        }); return c
                    }
                } function l(a, c, d, e) { function f() { n(c, a, d, e) } J && (200 <= a && 300 > a ? J.put(T, [a, c, md(d), e]) : J.remove(T)); b ? h.$applyAsync(f) : (f(), h.$$phase || h.$apply()) } function n(a, b, d, e) { b = -1 <= b ? b : 0; (200 <= b && 300 > b ? O.resolve : O.reject)({ data: a, status: b, headers: nd(d), config: c, statusText: e }) } function H(a) { n(a.data, a.status, ka(a.headers()), a.statusText) } function N() { var a = m.pendingRequests.indexOf(c); -1 !== a && m.pendingRequests.splice(a, 1) } var O = k.defer(), L = O.promise,
                    J, B, U = c.headers, T = r(c.url, c.paramSerializer(c.params)); m.pendingRequests.push(c); L.then(N, N); !c.cache && !a.cache || !1 === c.cache || "GET" !== c.method && "JSONP" !== c.method || (J = E(c.cache) ? c.cache : E(a.cache) ? a.cache : s); J && (B = J.get(T), x(B) ? B && D(B.then) ? B.then(H, H) : I(B) ? n(B[1], B[0], ka(B[2]), B[3]) : n(B, 200, {}, "OK") : J.put(T, L)); z(B) && ((B = qd(c.url) ? e()[c.xsrfCookieName || a.xsrfCookieName] : void 0) && (U[c.xsrfHeaderName || a.xsrfHeaderName] = B), f(c.method, T, d, l, U, c.timeout, c.withCredentials, c.responseType, g(c.eventHandlers),
                        g(c.uploadEventHandlers))); return L
            } function r(a, b) { 0 < b.length && (a += (-1 === a.indexOf("?") ? "?" : "&") + b); return a } var s = g("$http"); a.paramSerializer = C(a.paramSerializer) ? l.get(a.paramSerializer) : a.paramSerializer; var H = []; q(c, function (a) { H.unshift(C(a) ? l.get(a) : l.invoke(a)) }); m.pendingRequests = []; (function (a) { q(arguments, function (a) { m[a] = function (b, c) { return m(R({}, c || {}, { method: a, url: b })) } }) })("get", "delete", "head", "jsonp"); (function (a) {
                q(arguments, function (a) {
                    m[a] = function (b, c, d) {
                        return m(R({}, d || {},
                            { method: a, url: b, data: c }))
                    }
                })
            })("post", "put", "patch"); m.defaults = a; return m
        }]
    } function Jf() { this.$get = function () { return function () { return new w.XMLHttpRequest } } } function If() { this.$get = ["$browser", "$jsonpCallbacks", "$document", "$xhrFactory", function (a, b, d, c) { return yg(a, c, a.defer, b, d[0]) }] } function yg(a, b, d, c, f) {
        function e(a, b, d) {
            a = a.replace("JSON_CALLBACK", b); var e = f.createElement("script"), m = null; e.type = "text/javascript"; e.src = a; e.async = !0; m = function (a) {
                e.removeEventListener("load", m, !1); e.removeEventListener("error",
                    m, !1); f.body.removeChild(e); e = null; var g = -1, s = "unknown"; a && ("load" !== a.type || c.wasCalled(b) || (a = { type: "error" }), s = a.type, g = "error" === a.type ? 404 : 200); d && d(g, s)
            }; e.addEventListener("load", m, !1); e.addEventListener("error", m, !1); f.body.appendChild(e); return m
        } return function (f, h, k, l, m, n, r, s, H, u) {
            function p() { v && v(); M && M.abort() } function K(b, c, e, f, g) { x(O) && d.cancel(O); v = M = null; b(c, e, f, g); a.$$completeOutstandingRequest(y) } a.$$incOutstandingRequestCount(); h = h || a.url(); if ("jsonp" === Q(f)) var A = c.createCallback(h),
                v = e(h, A, function (a, b) { var d = 200 === a && c.getResponse(A); K(l, a, d, "", b); c.removeCallback(A) }); else {
                    var M = b(f, h); M.open(f, h, !0); q(m, function (a, b) { x(a) && M.setRequestHeader(b, a) }); M.onload = function () { var a = M.statusText || "", b = "response" in M ? M.response : M.responseText, c = 1223 === M.status ? 204 : M.status; 0 === c && (c = b ? 200 : "file" === ta(h).protocol ? 404 : 0); K(l, c, b, M.getAllResponseHeaders(), a) }; f = function () { K(l, -1, null, null, "") }; M.onerror = f; M.onabort = f; M.ontimeout = f; q(H, function (a, b) { M.addEventListener(b, a) }); q(u, function (a,
                        b) { M.upload.addEventListener(b, a) }); r && (M.withCredentials = !0); if (s) try { M.responseType = s } catch (N) { if ("json" !== s) throw N; } M.send(z(k) ? null : k)
            } if (0 < n) var O = d(p, n); else n && D(n.then) && n.then(p)
        }
    } function Df() {
        var a = "{{", b = "}}"; this.startSymbol = function (b) { return b ? (a = b, this) : a }; this.endSymbol = function (a) { return a ? (b = a, this) : b }; this.$get = ["$parse", "$exceptionHandler", "$sce", function (d, c, f) {
            function e(a) { return "\\\\\\" + a } function g(c) { return c.replace(n, a).replace(r, b) } function h(a, b, c, d) {
                var e = a.$watch(function (a) {
                    e();
                    return d(a)
                }, b, c); return e
            } function k(e, k, n, p) {
                function r(a) { try { var b = a; a = n ? f.getTrusted(n, b) : f.valueOf(b); var d; if (p && !x(a)) d = a; else if (null == a) d = ""; else { switch (typeof a) { case "string": break; case "number": a = "" + a; break; default: a = eb(a) }d = a } return d } catch (g) { c(Ia.interr(e, g)) } } if (!e.length || -1 === e.indexOf(a)) { var A; k || (k = g(e), A = ga(k), A.exp = e, A.expressions = [], A.$$watchDelegate = h); return A } p = !!p; var v, q, N = 0, O = [], L = []; A = e.length; for (var J = [], B = []; N < A;)if (-1 !== (v = e.indexOf(a, N)) && -1 !== (q = e.indexOf(b, v +
                    l))) N !== v && J.push(g(e.substring(N, v))), N = e.substring(v + l, q), O.push(N), L.push(d(N, r)), N = q + m, B.push(J.length), J.push(""); else { N !== A && J.push(g(e.substring(N))); break } n && 1 < J.length && Ia.throwNoconcat(e); if (!k || O.length) {
                        var U = function (a) { for (var b = 0, c = O.length; b < c; b++) { if (p && z(a[b])) return; J[B[b]] = a[b] } return J.join("") }; return R(function (a) { var b = 0, d = O.length, f = Array(d); try { for (; b < d; b++)f[b] = L[b](a); return U(f) } catch (g) { c(Ia.interr(e, g)) } }, {
                            exp: e, expressions: O, $$watchDelegate: function (a, b) {
                                var c; return a.$watchGroup(L,
                                    function (d, e) { var f = U(d); D(b) && b.call(this, f, d !== e ? c : f, a); c = f })
                            }
                        })
                    }
            } var l = a.length, m = b.length, n = new RegExp(a.replace(/./g, e), "g"), r = new RegExp(b.replace(/./g, e), "g"); k.startSymbol = function () { return a }; k.endSymbol = function () { return b }; return k
        }]
    } function Ef() {
        this.$get = ["$rootScope", "$window", "$q", "$$q", "$browser", function (a, b, d, c, f) {
            function e(e, k, l, m) {
                function n() { r ? e.apply(null, s) : e(p) } var r = 4 < arguments.length, s = r ? va.call(arguments, 4) : [], H = b.setInterval, u = b.clearInterval, p = 0, K = x(m) && !m, A = (K ? c : d).defer(),
                    v = A.promise; l = x(l) ? l : 0; v.$$intervalId = H(function () { K ? f.defer(n) : a.$evalAsync(n); A.notify(p++); 0 < l && p >= l && (A.resolve(p), u(v.$$intervalId), delete g[v.$$intervalId]); K || a.$apply() }, k); g[v.$$intervalId] = A; return v
            } var g = {}; e.cancel = function (a) { return a && a.$$intervalId in g ? (g[a.$$intervalId].reject("canceled"), b.clearInterval(a.$$intervalId), delete g[a.$$intervalId], !0) : !1 }; return e
        }]
    } function jc(a) { a = a.split("/"); for (var b = a.length; b--;)a[b] = ub(a[b]); return a.join("/") } function rd(a, b) {
        var d = ta(a); b.$$protocol =
            d.protocol; b.$$host = d.hostname; b.$$port = Z(d.port) || zg[d.protocol] || null
    } function sd(a, b) { if (Ag.test(a)) throw mb("badpath", a); var d = "/" !== a.charAt(0); d && (a = "/" + a); var c = ta(a); b.$$path = decodeURIComponent(d && "/" === c.pathname.charAt(0) ? c.pathname.substring(1) : c.pathname); b.$$search = Hc(c.search); b.$$hash = decodeURIComponent(c.hash); b.$$path && "/" !== b.$$path.charAt(0) && (b.$$path = "/" + b.$$path) } function qa(a, b) { if (b.slice(0, a.length) === a) return b.substr(a.length) } function Ha(a) {
        var b = a.indexOf("#"); return -1 ===
            b ? a : a.substr(0, b)
    } function nb(a) { return a.replace(/(#.+)|#$/, "$1") } function kc(a, b, d) {
        this.$$html5 = !0; d = d || ""; rd(a, this); this.$$parse = function (a) { var d = qa(b, a); if (!C(d)) throw mb("ipthprfx", a, b); sd(d, this); this.$$path || (this.$$path = "/"); this.$$compose() }; this.$$compose = function () { var a = Wb(this.$$search), d = this.$$hash ? "#" + ub(this.$$hash) : ""; this.$$url = jc(this.$$path) + (a ? "?" + a : "") + d; this.$$absUrl = b + this.$$url.substr(1) }; this.$$parseLinkUrl = function (c, f) {
            if (f && "#" === f[0]) return this.hash(f.slice(1)),
                !0; var e, g; x(e = qa(a, c)) ? (g = e, g = d && x(e = qa(d, e)) ? b + (qa("/", e) || e) : a + g) : x(e = qa(b, c)) ? g = b + e : b === c + "/" && (g = b); g && this.$$parse(g); return !!g
        }
    } function lc(a, b, d) {
        rd(a, this); this.$$parse = function (c) { var f = qa(a, c) || qa(b, c), e; z(f) || "#" !== f.charAt(0) ? this.$$html5 ? e = f : (e = "", z(f) && (a = c, this.replace())) : (e = qa(d, f), z(e) && (e = f)); sd(e, this); c = this.$$path; var f = a, g = /^\/[A-Z]:(\/.*)/; e.slice(0, f.length) === f && (e = e.replace(f, "")); g.exec(e) || (c = (e = g.exec(c)) ? e[1] : c); this.$$path = c; this.$$compose() }; this.$$compose = function () {
            var b =
                Wb(this.$$search), f = this.$$hash ? "#" + ub(this.$$hash) : ""; this.$$url = jc(this.$$path) + (b ? "?" + b : "") + f; this.$$absUrl = a + (this.$$url ? d + this.$$url : "")
        }; this.$$parseLinkUrl = function (b, d) { return Ha(a) === Ha(b) ? (this.$$parse(b), !0) : !1 }
    } function td(a, b, d) {
        this.$$html5 = !0; lc.apply(this, arguments); this.$$parseLinkUrl = function (c, f) { if (f && "#" === f[0]) return this.hash(f.slice(1)), !0; var e, g; a === Ha(c) ? e = c : (g = qa(b, c)) ? e = a + d + g : b === c + "/" && (e = b); e && this.$$parse(e); return !!e }; this.$$compose = function () {
            var b = Wb(this.$$search),
            f = this.$$hash ? "#" + ub(this.$$hash) : ""; this.$$url = jc(this.$$path) + (b ? "?" + b : "") + f; this.$$absUrl = a + d + this.$$url
        }
    } function Jb(a) { return function () { return this[a] } } function ud(a, b) { return function (d) { if (z(d)) return this[a]; this[a] = b(d); this.$$compose(); return this } } function Lf() {
        var a = "", b = { enabled: !1, requireBase: !0, rewriteLinks: !0 }; this.hashPrefix = function (b) { return x(b) ? (a = b, this) : a }; this.html5Mode = function (a) {
            if (Ka(a)) return b.enabled = a, this; if (E(a)) {
                Ka(a.enabled) && (b.enabled = a.enabled); Ka(a.requireBase) &&
                    (b.requireBase = a.requireBase); if (Ka(a.rewriteLinks) || C(a.rewriteLinks)) b.rewriteLinks = a.rewriteLinks; return this
            } return b
        }; this.$get = ["$rootScope", "$browser", "$sniffer", "$rootElement", "$window", function (d, c, f, e, g) {
            function h(a, b, d) { var e = l.url(), f = l.$$state; try { c.url(a, b, d), l.$$state = c.state() } catch (g) { throw l.url(e), l.$$state = f, g; } } function k(a, b) { d.$broadcast("$locationChangeSuccess", l.absUrl(), a, l.$$state, b) } var l, m; m = c.baseHref(); var n = c.url(), r; if (b.enabled) {
                if (!m && b.requireBase) throw mb("nobase");
                r = n.substring(0, n.indexOf("/", n.indexOf("//") + 2)) + (m || "/"); m = f.history ? kc : td
            } else r = Ha(n), m = lc; var s = r.substr(0, Ha(r).lastIndexOf("/") + 1); l = new m(r, s, "#" + a); l.$$parseLinkUrl(n, n); l.$$state = c.state(); var H = /^\s*(javascript|mailto):/i; e.on("click", function (a) {
                var f = b.rewriteLinks; if (f && !a.ctrlKey && !a.metaKey && !a.shiftKey && 2 !== a.which && 2 !== a.button) {
                    for (var g = F(a.target); "a" !== wa(g[0]);)if (g[0] === e[0] || !(g = g.parent())[0]) return; if (!C(f) || !z(g.attr(f))) {
                        var f = g.prop("href"), h = g.attr("href") || g.attr("xlink:href");
                        E(f) && "[object SVGAnimatedString]" === f.toString() && (f = ta(f.animVal).href); H.test(f) || !f || g.attr("target") || a.isDefaultPrevented() || !l.$$parseLinkUrl(f, h) || (a.preventDefault(), l.absUrl() !== c.url() && d.$apply())
                    }
                }
            }); nb(l.absUrl()) !== nb(n) && c.url(l.absUrl(), !0); var u = !0; c.onUrlChange(function (a, b) {
                z(qa(s, a)) ? g.location.href = a : (d.$evalAsync(function () {
                    var c = l.absUrl(), e = l.$$state, f; a = nb(a); l.$$parse(a); l.$$state = b; f = d.$broadcast("$locationChangeStart", a, c, b, e).defaultPrevented; l.absUrl() === a && (f ? (l.$$parse(c),
                        l.$$state = e, h(c, !1, e)) : (u = !1, k(c, e)))
                }), d.$$phase || d.$digest())
            }); d.$watch(function () { var a = nb(c.url()), b = nb(l.absUrl()), e = c.state(), g = l.$$replace, m = a !== b || l.$$html5 && f.history && e !== l.$$state; if (u || m) u = !1, d.$evalAsync(function () { var b = l.absUrl(), c = d.$broadcast("$locationChangeStart", b, a, l.$$state, e).defaultPrevented; l.absUrl() === b && (c ? (l.$$parse(a), l.$$state = e) : (m && h(b, g, e === l.$$state ? null : l.$$state), k(a, e))) }); l.$$replace = !1 }); return l
        }]
    } function Mf() {
        var a = !0, b = this; this.debugEnabled = function (b) {
            return x(b) ?
                (a = b, this) : a
        }; this.$get = ["$window", function (d) {
            function c(a) { a instanceof Error && (a.stack ? a = a.message && -1 === a.stack.indexOf(a.message) ? "Error: " + a.message + "\n" + a.stack : a.stack : a.sourceURL && (a = a.message + "\n" + a.sourceURL + ":" + a.line)); return a } function f(a) { var b = d.console || {}, f = b[a] || b.log || y; a = !1; try { a = !!f.apply } catch (k) { } return a ? function () { var a = []; q(arguments, function (b) { a.push(c(b)) }); return f.apply(b, a) } : function (a, b) { f(a, null == b ? "" : b) } } return {
                log: f("log"), info: f("info"), warn: f("warn"), error: f("error"),
                debug: function () { var c = f("debug"); return function () { a && c.apply(b, arguments) } }()
            }
        }]
    } function Ua(a, b) { if ("__defineGetter__" === a || "__defineSetter__" === a || "__lookupGetter__" === a || "__lookupSetter__" === a || "__proto__" === a) throw ea("isecfld", b); return a } function Bg(a) { return a + "" } function Ea(a, b) { if (a) { if (a.constructor === a) throw ea("isecfn", b); if (a.window === a) throw ea("isecwindow", b); if (a.children && (a.nodeName || a.prop && a.attr && a.find)) throw ea("isecdom", b); if (a === Object) throw ea("isecobj", b); } return a } function vd(a,
        b) { if (a) { if (a.constructor === a) throw ea("isecfn", b); if (a === Cg || a === Dg || a === Eg) throw ea("isecff", b); } } function Kb(a, b) { if (a && (a === wd || a === xd || a === yd || a === zd || a === Ad || a === Bd || a === Fg || a === Gg || a === Lb || a === Hg || a === Cd || a === Ig)) throw ea("isecaf", b); } function Jg(a, b) { return "undefined" !== typeof a ? a : b } function Dd(a, b) { return "undefined" === typeof a ? b : "undefined" === typeof b ? a : a + b } function X(a, b) {
            var d, c, f; switch (a.type) {
                case t.Program: d = !0; q(a.body, function (a) { X(a.expression, b); d = d && a.expression.constant }); a.constant =
                    d; break; case t.Literal: a.constant = !0; a.toWatch = []; break; case t.UnaryExpression: X(a.argument, b); a.constant = a.argument.constant; a.toWatch = a.argument.toWatch; break; case t.BinaryExpression: X(a.left, b); X(a.right, b); a.constant = a.left.constant && a.right.constant; a.toWatch = a.left.toWatch.concat(a.right.toWatch); break; case t.LogicalExpression: X(a.left, b); X(a.right, b); a.constant = a.left.constant && a.right.constant; a.toWatch = a.constant ? [] : [a]; break; case t.ConditionalExpression: X(a.test, b); X(a.alternate, b); X(a.consequent,
                        b); a.constant = a.test.constant && a.alternate.constant && a.consequent.constant; a.toWatch = a.constant ? [] : [a]; break; case t.Identifier: a.constant = !1; a.toWatch = [a]; break; case t.MemberExpression: X(a.object, b); a.computed && X(a.property, b); a.constant = a.object.constant && (!a.computed || a.property.constant); a.toWatch = [a]; break; case t.CallExpression: d = f = a.filter ? !b(a.callee.name).$stateful : !1; c = []; q(a.arguments, function (a) { X(a, b); d = d && a.constant; a.constant || c.push.apply(c, a.toWatch) }); a.constant = d; a.toWatch = f ? c : [a];
                    break; case t.AssignmentExpression: X(a.left, b); X(a.right, b); a.constant = a.left.constant && a.right.constant; a.toWatch = [a]; break; case t.ArrayExpression: d = !0; c = []; q(a.elements, function (a) { X(a, b); d = d && a.constant; a.constant || c.push.apply(c, a.toWatch) }); a.constant = d; a.toWatch = c; break; case t.ObjectExpression: d = !0; c = []; q(a.properties, function (a) { X(a.value, b); d = d && a.value.constant && !a.computed; a.value.constant || c.push.apply(c, a.value.toWatch) }); a.constant = d; a.toWatch = c; break; case t.ThisExpression: a.constant =
                        !1; a.toWatch = []; break; case t.LocalsExpression: a.constant = !1, a.toWatch = []
            }
        } function Ed(a) { if (1 === a.length) { a = a[0].expression; var b = a.toWatch; return 1 !== b.length ? b : b[0] !== a ? b : void 0 } } function Fd(a) { return a.type === t.Identifier || a.type === t.MemberExpression } function Gd(a) { if (1 === a.body.length && Fd(a.body[0].expression)) return { type: t.AssignmentExpression, left: a.body[0].expression, right: { type: t.NGValueParameter }, operator: "=" } } function Hd(a) {
            return 0 === a.body.length || 1 === a.body.length && (a.body[0].expression.type ===
                t.Literal || a.body[0].expression.type === t.ArrayExpression || a.body[0].expression.type === t.ObjectExpression)
        } function Id(a, b) { this.astBuilder = a; this.$filter = b } function Jd(a, b) { this.astBuilder = a; this.$filter = b } function Mb(a) { return "constructor" === a } function mc(a) { return D(a.valueOf) ? a.valueOf() : Kg.call(a) } function Nf() {
            var a = W(), b = W(), d = { "true": !0, "false": !1, "null": null, undefined: void 0 }, c, f; this.addLiteral = function (a, b) { d[a] = b }; this.setIdentifierFns = function (a, b) { c = a; f = b; return this }; this.$get = ["$filter",
                function (e) {
                    function g(c, d, f) { var g, k, H; f = f || K; switch (typeof c) { case "string": H = c = c.trim(); var q = f ? b : a; g = q[H]; if (!g) { ":" === c.charAt(0) && ":" === c.charAt(1) && (k = !0, c = c.substring(2)); g = f ? p : u; var B = new nc(g); g = (new oc(B, e, g)).parse(c); g.constant ? g.$$watchDelegate = r : k ? g.$$watchDelegate = g.literal ? n : m : g.inputs && (g.$$watchDelegate = l); f && (g = h(g)); q[H] = g } return s(g, d); case "function": return s(c, d); default: return s(y, d) } } function h(a) {
                        function b(c, d, e, f) { var g = K; K = !0; try { return a(c, d, e, f) } finally { K = g } } if (!a) return a;
                        b.$$watchDelegate = a.$$watchDelegate; b.assign = h(a.assign); b.constant = a.constant; b.literal = a.literal; for (var c = 0; a.inputs && c < a.inputs.length; ++c)a.inputs[c] = h(a.inputs[c]); b.inputs = a.inputs; return b
                    } function k(a, b) { return null == a || null == b ? a === b : "object" === typeof a && (a = mc(a), "object" === typeof a) ? !1 : a === b || a !== a && b !== b } function l(a, b, c, d, e) {
                        var f = d.inputs, g; if (1 === f.length) { var h = k, f = f[0]; return a.$watch(function (a) { var b = f(a); k(b, h) || (g = d(a, void 0, void 0, [b]), h = b && mc(b)); return g }, b, c, e) } for (var l = [],
                            m = [], n = 0, s = f.length; n < s; n++)l[n] = k, m[n] = null; return a.$watch(function (a) { for (var b = !1, c = 0, e = f.length; c < e; c++) { var h = f[c](a); if (b || (b = !k(h, l[c]))) m[c] = h, l[c] = h && mc(h) } b && (g = d(a, void 0, void 0, m)); return g }, b, c, e)
                    } function m(a, b, c, d) { var e, f; return e = a.$watch(function (a) { return d(a) }, function (a, c, d) { f = a; D(b) && b.apply(this, arguments); x(a) && d.$$postDigest(function () { x(f) && e() }) }, c) } function n(a, b, c, d) {
                        function e(a) { var b = !0; q(a, function (a) { x(a) || (b = !1) }); return b } var f, g; return f = a.$watch(function (a) { return d(a) },
                            function (a, c, d) { g = a; D(b) && b.call(this, a, c, d); e(a) && d.$$postDigest(function () { e(g) && f() }) }, c)
                    } function r(a, b, c, d) { var e = a.$watch(function (a) { e(); return d(a) }, b, c); return e } function s(a, b) {
                        if (!b) return a; var c = a.$$watchDelegate, d = !1, c = c !== n && c !== m ? function (c, e, f, g) { f = d && g ? g[0] : a(c, e, f, g); return b(f, c, e) } : function (c, d, e, f) { e = a(c, d, e, f); c = b(e, c, d); return x(e) ? c : e }; a.$$watchDelegate && a.$$watchDelegate !== l ? c.$$watchDelegate = a.$$watchDelegate : b.$stateful || (c.$$watchDelegate = l, d = !a.inputs, c.inputs = a.inputs ?
                            a.inputs : [a]); return c
                    } var H = da().noUnsafeEval, u = { csp: H, expensiveChecks: !1, literals: sa(d), isIdentifierStart: D(c) && c, isIdentifierContinue: D(f) && f }, p = { csp: H, expensiveChecks: !0, literals: sa(d), isIdentifierStart: D(c) && c, isIdentifierContinue: D(f) && f }, K = !1; g.$$runningExpensiveChecks = function () { return K }; return g
                }]
        } function Pf() { this.$get = ["$rootScope", "$exceptionHandler", function (a, b) { return Kd(function (b) { a.$evalAsync(b) }, b) }] } function Qf() {
            this.$get = ["$browser", "$exceptionHandler", function (a, b) {
                return Kd(function (b) { a.defer(b) },
                    b)
            }]
        } function Kd(a, b) {
            function d() { var a = new g; a.resolve = f(a, a.resolve); a.reject = f(a, a.reject); a.notify = f(a, a.notify); return a } function c() { this.$$state = { status: 0 } } function f(a, b) { return function (c) { b.call(a, c) } } function e(c) {
                !c.processScheduled && c.pending && (c.processScheduled = !0, a(function () {
                    var a, d, e; e = c.pending; c.processScheduled = !1; c.pending = void 0; for (var f = 0, g = e.length; f < g; ++f) {
                        d = e[f][0]; a = e[f][c.status]; try { D(a) ? d.resolve(a(c.value)) : 1 === c.status ? d.resolve(c.value) : d.reject(c.value) } catch (h) {
                            d.reject(h),
                            b(h)
                        }
                    }
                }))
            } function g() { this.promise = new c } function h(a) { var b = new g; b.reject(a); return b.promise } function k(a, b, c) { var d = null; try { D(c) && (d = c()) } catch (e) { return h(e) } return d && D(d.then) ? d.then(function () { return b(a) }, h) : b(a) } function l(a, b, c, d) { var e = new g; e.resolve(a); return e.promise.then(b, c, d) } function m(a) { if (!D(a)) throw n("norslvr", a); var b = new g; a(function (a) { b.resolve(a) }, function (a) { b.reject(a) }); return b.promise } var n = G("$q", TypeError); R(c.prototype, {
                then: function (a, b, c) {
                    if (z(a) && z(b) && z(c)) return this;
                    var d = new g; this.$$state.pending = this.$$state.pending || []; this.$$state.pending.push([d, a, b, c]); 0 < this.$$state.status && e(this.$$state); return d.promise
                }, "catch": function (a) { return this.then(null, a) }, "finally": function (a, b) { return this.then(function (b) { return k(b, r, a) }, function (b) { return k(b, h, a) }, b) }
            }); R(g.prototype, {
                resolve: function (a) { this.promise.$$state.status || (a === this.promise ? this.$$reject(n("qcycle", a)) : this.$$resolve(a)) }, $$resolve: function (a) {
                    function c(a) { k || (k = !0, h.$$resolve(a)) } function d(a) {
                        k ||
                        (k = !0, h.$$reject(a))
                    } var g, h = this, k = !1; try { if (E(a) || D(a)) g = a && a.then; D(g) ? (this.promise.$$state.status = -1, g.call(a, c, d, f(this, this.notify))) : (this.promise.$$state.value = a, this.promise.$$state.status = 1, e(this.promise.$$state)) } catch (l) { d(l), b(l) }
                }, reject: function (a) { this.promise.$$state.status || this.$$reject(a) }, $$reject: function (a) { this.promise.$$state.value = a; this.promise.$$state.status = 2; e(this.promise.$$state) }, notify: function (c) {
                    var d = this.promise.$$state.pending; 0 >= this.promise.$$state.status &&
                        d && d.length && a(function () { for (var a, e, f = 0, g = d.length; f < g; f++) { e = d[f][0]; a = d[f][3]; try { e.notify(D(a) ? a(c) : c) } catch (h) { b(h) } } })
                }
            }); var r = l; m.prototype = c.prototype; m.defer = d; m.reject = h; m.when = l; m.resolve = r; m.all = function (a) { var b = new g, c = 0, d = I(a) ? [] : {}; q(a, function (a, e) { c++; l(a).then(function (a) { d[e] = a; --c || b.resolve(d) }, function (a) { b.reject(a) }) }); 0 === c && b.resolve(d); return b.promise }; m.race = function (a) { var b = d(); q(a, function (a) { l(a).then(b.resolve, b.reject) }); return b.promise }; return m
        } function Zf() {
            this.$get =
            ["$window", "$timeout", function (a, b) { var d = a.requestAnimationFrame || a.webkitRequestAnimationFrame, c = a.cancelAnimationFrame || a.webkitCancelAnimationFrame || a.webkitCancelRequestAnimationFrame, f = !!d, e = f ? function (a) { var b = d(a); return function () { c(b) } } : function (a) { var c = b(a, 16.66, !1); return function () { b.cancel(c) } }; e.supported = f; return e }]
        } function Of() {
            function a(a) {
                function b() {
                    this.$$watchers = this.$$nextSibling = this.$$childHead = this.$$childTail = null; this.$$listeners = {}; this.$$listenerCount = {}; this.$$watchersCount =
                        0; this.$id = ++tb; this.$$ChildScope = null
                } b.prototype = a; return b
            } var b = 10, d = G("$rootScope"), c = null, f = null; this.digestTtl = function (a) { arguments.length && (b = a); return b }; this.$get = ["$exceptionHandler", "$parse", "$browser", function (e, g, h) {
                function k(a) { a.currentScope.$$destroyed = !0 } function l(a) { 9 === Ga && (a.$$childHead && l(a.$$childHead), a.$$nextSibling && l(a.$$nextSibling)); a.$parent = a.$$nextSibling = a.$$prevSibling = a.$$childHead = a.$$childTail = a.$root = a.$$watchers = null } function m() {
                    this.$id = ++tb; this.$$phase =
                        this.$parent = this.$$watchers = this.$$nextSibling = this.$$prevSibling = this.$$childHead = this.$$childTail = null; this.$root = this; this.$$destroyed = !1; this.$$listeners = {}; this.$$listenerCount = {}; this.$$watchersCount = 0; this.$$isolateBindings = null
                } function n(a) { if (K.$$phase) throw d("inprog", K.$$phase); K.$$phase = a } function r(a, b) { do a.$$watchersCount += b; while (a = a.$parent) } function s(a, b, c) { do a.$$listenerCount[c] -= b, 0 === a.$$listenerCount[c] && delete a.$$listenerCount[c]; while (a = a.$parent) } function H() { } function u() {
                    for (; t.length;)try { t.shift()() } catch (a) { e(a) } f =
                        null
                } function p() { null === f && (f = h.defer(function () { K.$apply(u) })) } m.prototype = {
                    constructor: m, $new: function (b, c) { var d; c = c || this; b ? (d = new m, d.$root = this.$root) : (this.$$ChildScope || (this.$$ChildScope = a(this)), d = new this.$$ChildScope); d.$parent = c; d.$$prevSibling = c.$$childTail; c.$$childHead ? (c.$$childTail.$$nextSibling = d, c.$$childTail = d) : c.$$childHead = c.$$childTail = d; (b || c !== this) && d.$on("$destroy", k); return d }, $watch: function (a, b, d, e) {
                        var f = g(a); if (f.$$watchDelegate) return f.$$watchDelegate(this, b, d,
                            f, a); var h = this, k = h.$$watchers, l = { fn: b, last: H, get: f, exp: e || a, eq: !!d }; c = null; D(b) || (l.fn = y); k || (k = h.$$watchers = [], k.$$digestWatchIndex = -1); k.unshift(l); k.$$digestWatchIndex++; r(this, 1); return function () { var a = bb(k, l); 0 <= a && (r(h, -1), a < k.$$digestWatchIndex && k.$$digestWatchIndex--); c = null }
                    }, $watchGroup: function (a, b) {
                        function c() { h = !1; k ? (k = !1, b(e, e, g)) : b(e, d, g) } var d = Array(a.length), e = Array(a.length), f = [], g = this, h = !1, k = !0; if (!a.length) {
                            var l = !0; g.$evalAsync(function () { l && b(e, e, g) }); return function () {
                                l =
                                !1
                            }
                        } if (1 === a.length) return this.$watch(a[0], function (a, c, f) { e[0] = a; d[0] = c; b(e, a === c ? e : d, f) }); q(a, function (a, b) { var k = g.$watch(a, function (a, f) { e[b] = a; d[b] = f; h || (h = !0, g.$evalAsync(c)) }); f.push(k) }); return function () { for (; f.length;)f.shift()() }
                    }, $watchCollection: function (a, b) {
                        function c(a) {
                            e = a; var b, d, g, h; if (!z(e)) {
                                if (E(e)) if (la(e)) for (f !== n && (f = n, s = f.length = 0, l++), a = e.length, s !== a && (l++, f.length = s = a), b = 0; b < a; b++)h = f[b], g = e[b], d = h !== h && g !== g, d || h === g || (l++, f[b] = g); else {
                                    f !== r && (f = r = {}, s = 0, l++); a = 0; for (b in e) ua.call(e,
                                        b) && (a++, g = e[b], h = f[b], b in f ? (d = h !== h && g !== g, d || h === g || (l++, f[b] = g)) : (s++, f[b] = g, l++)); if (s > a) for (b in l++, f) ua.call(e, b) || (s--, delete f[b])
                                } else f !== e && (f = e, l++); return l
                            }
                        } c.$stateful = !0; var d = this, e, f, h, k = 1 < b.length, l = 0, m = g(a, c), n = [], r = {}, p = !0, s = 0; return this.$watch(m, function () { p ? (p = !1, b(e, e, d)) : b(e, h, d); if (k) if (E(e)) if (la(e)) { h = Array(e.length); for (var a = 0; a < e.length; a++)h[a] = e[a] } else for (a in h = {}, e) ua.call(e, a) && (h[a] = e[a]); else h = e })
                    }, $digest: function () {
                        var a, g, k, l, m, r, p, s = b, q, t = [], M, x; n("$digest");
                        h.$$checkUrlChange(); this === K && null !== f && (h.defer.cancel(f), u()); c = null; do {
                            p = !1; q = this; for (r = 0; r < A.length; r++) { try { x = A[r], x.scope.$eval(x.expression, x.locals) } catch (w) { e(w) } c = null } A.length = 0; a: do {
                                if (r = q.$$watchers) for (r.$$digestWatchIndex = r.length; r.$$digestWatchIndex--;)try {
                                    if (a = r[r.$$digestWatchIndex]) if (m = a.get, (g = m(q)) !== (k = a.last) && !(a.eq ? na(g, k) : ha(g) && ha(k))) p = !0, c = a, a.last = a.eq ? sa(g, null) : g, l = a.fn, l(g, k === H ? g : k, q), 5 > s && (M = 4 - s, t[M] || (t[M] = []), t[M].push({
                                        msg: D(a.exp) ? "fn: " + (a.exp.name || a.exp.toString()) :
                                            a.exp, newVal: g, oldVal: k
                                    })); else if (a === c) { p = !1; break a }
                                } catch (z) { e(z) } if (!(r = q.$$watchersCount && q.$$childHead || q !== this && q.$$nextSibling)) for (; q !== this && !(r = q.$$nextSibling);)q = q.$parent
                            } while (q = r); if ((p || A.length) && !s--) throw K.$$phase = null, d("infdig", b, t);
                        } while (p || A.length); for (K.$$phase = null; N < v.length;)try { v[N++]() } catch (y) { e(y) } v.length = N = 0
                    }, $destroy: function () {
                        if (!this.$$destroyed) {
                            var a = this.$parent; this.$broadcast("$destroy"); this.$$destroyed = !0; this === K && h.$$applicationDestroyed(); r(this,
                                -this.$$watchersCount); for (var b in this.$$listenerCount) s(this, this.$$listenerCount[b], b); a && a.$$childHead === this && (a.$$childHead = this.$$nextSibling); a && a.$$childTail === this && (a.$$childTail = this.$$prevSibling); this.$$prevSibling && (this.$$prevSibling.$$nextSibling = this.$$nextSibling); this.$$nextSibling && (this.$$nextSibling.$$prevSibling = this.$$prevSibling); this.$destroy = this.$digest = this.$apply = this.$evalAsync = this.$applyAsync = y; this.$on = this.$watch = this.$watchGroup = function () { return y }; this.$$listeners =
                                    {}; this.$$nextSibling = null; l(this)
                        }
                    }, $eval: function (a, b) { return g(a)(this, b) }, $evalAsync: function (a, b) { K.$$phase || A.length || h.defer(function () { A.length && K.$digest() }); A.push({ scope: this, expression: g(a), locals: b }) }, $$postDigest: function (a) { v.push(a) }, $apply: function (a) { try { n("$apply"); try { return this.$eval(a) } finally { K.$$phase = null } } catch (b) { e(b) } finally { try { K.$digest() } catch (c) { throw e(c), c; } } }, $applyAsync: function (a) { function b() { c.$eval(a) } var c = this; a && t.push(b); a = g(a); p() }, $on: function (a, b) {
                        var c =
                            this.$$listeners[a]; c || (this.$$listeners[a] = c = []); c.push(b); var d = this; do d.$$listenerCount[a] || (d.$$listenerCount[a] = 0), d.$$listenerCount[a]++; while (d = d.$parent); var e = this; return function () { var d = c.indexOf(b); -1 !== d && (c[d] = null, s(e, 1, a)) }
                    }, $emit: function (a, b) {
                        var c = [], d, f = this, g = !1, h = { name: a, targetScope: f, stopPropagation: function () { g = !0 }, preventDefault: function () { h.defaultPrevented = !0 }, defaultPrevented: !1 }, k = cb([h], arguments, 1), l, m; do {
                            d = f.$$listeners[a] || c; h.currentScope = f; l = 0; for (m = d.length; l <
                                m; l++)if (d[l]) try { d[l].apply(null, k) } catch (n) { e(n) } else d.splice(l, 1), l--, m--; if (g) return h.currentScope = null, h; f = f.$parent
                        } while (f); h.currentScope = null; return h
                    }, $broadcast: function (a, b) {
                        var c = this, d = this, f = { name: a, targetScope: this, preventDefault: function () { f.defaultPrevented = !0 }, defaultPrevented: !1 }; if (!this.$$listenerCount[a]) return f; for (var g = cb([f], arguments, 1), h, k; c = d;) {
                            f.currentScope = c; d = c.$$listeners[a] || []; h = 0; for (k = d.length; h < k; h++)if (d[h]) try { d[h].apply(null, g) } catch (l) { e(l) } else d.splice(h,
                                1), h--, k--; if (!(d = c.$$listenerCount[a] && c.$$childHead || c !== this && c.$$nextSibling)) for (; c !== this && !(d = c.$$nextSibling);)c = c.$parent
                        } f.currentScope = null; return f
                    }
                }; var K = new m, A = K.$$asyncQueue = [], v = K.$$postDigestQueue = [], t = K.$$applyAsyncQueue = [], N = 0; return K
            }]
        } function He() {
            var a = /^\s*(https?|ftp|mailto|tel|file):/, b = /^\s*((https?|ftp|file|blob):|data:image\/)/; this.aHrefSanitizationWhitelist = function (b) { return x(b) ? (a = b, this) : a }; this.imgSrcSanitizationWhitelist = function (a) { return x(a) ? (b = a, this) : b };
            this.$get = function () { return function (d, c) { var f = c ? b : a, e; e = ta(d && d.trim()).href; return "" === e || e.match(f) ? d : "unsafe:" + e } }
        } function Lg(a) { if ("self" === a) return a; if (C(a)) { if (-1 < a.indexOf("***")) throw Fa("iwcard", a); a = Ld(a).replace(/\\\*\\\*/g, ".*").replace(/\\\*/g, "[^:/.?&;]*"); return new RegExp("^" + a + "$") } if (Za(a)) return new RegExp("^" + a.source + "$"); throw Fa("imatcher"); } function Md(a) { var b = []; x(a) && q(a, function (a) { b.push(Lg(a)) }); return b } function Sf() {
            this.SCE_CONTEXTS = ra; var a = ["self"], b = []; this.resourceUrlWhitelist =
                function (b) { arguments.length && (a = Md(b)); return a }; this.resourceUrlBlacklist = function (a) { arguments.length && (b = Md(a)); return b }; this.$get = ["$injector", function (d) {
                    function c(a, b) { return "self" === a ? qd(b) : !!a.exec(b.href) } function f(a) { var b = function (a) { this.$$unwrapTrustedValue = function () { return a } }; a && (b.prototype = new a); b.prototype.valueOf = function () { return this.$$unwrapTrustedValue() }; b.prototype.toString = function () { return this.$$unwrapTrustedValue().toString() }; return b } var e = function (a) {
                        throw Fa("unsafe");
                    }; d.has("$sanitize") && (e = d.get("$sanitize")); var g = f(), h = {}; h[ra.HTML] = f(g); h[ra.CSS] = f(g); h[ra.URL] = f(g); h[ra.JS] = f(g); h[ra.RESOURCE_URL] = f(h[ra.URL]); return {
                        trustAs: function (a, b) { var c = h.hasOwnProperty(a) ? h[a] : null; if (!c) throw Fa("icontext", a, b); if (null === b || z(b) || "" === b) return b; if ("string" !== typeof b) throw Fa("itype", a); return new c(b) }, getTrusted: function (d, f) {
                            if (null === f || z(f) || "" === f) return f; var g = h.hasOwnProperty(d) ? h[d] : null; if (g && f instanceof g) return f.$$unwrapTrustedValue(); if (d === ra.RESOURCE_URL) {
                                var g =
                                    ta(f.toString()), n, r, s = !1; n = 0; for (r = a.length; n < r; n++)if (c(a[n], g)) { s = !0; break } if (s) for (n = 0, r = b.length; n < r; n++)if (c(b[n], g)) { s = !1; break } if (s) return f; throw Fa("insecurl", f.toString());
                            } if (d === ra.HTML) return e(f); throw Fa("unsafe");
                        }, valueOf: function (a) { return a instanceof g ? a.$$unwrapTrustedValue() : a }
                    }
                }]
        } function Rf() {
            var a = !0; this.enabled = function (b) { arguments.length && (a = !!b); return a }; this.$get = ["$parse", "$sceDelegate", function (b, d) {
                if (a && 8 > Ga) throw Fa("iequirks"); var c = ka(ra); c.isEnabled = function () { return a };
                c.trustAs = d.trustAs; c.getTrusted = d.getTrusted; c.valueOf = d.valueOf; a || (c.trustAs = c.getTrusted = function (a, b) { return b }, c.valueOf = $a); c.parseAs = function (a, d) { var e = b(d); return e.literal && e.constant ? e : b(d, function (b) { return c.getTrusted(a, b) }) }; var f = c.parseAs, e = c.getTrusted, g = c.trustAs; q(ra, function (a, b) { var d = Q(b); c[hb("parse_as_" + d)] = function (b) { return f(a, b) }; c[hb("get_trusted_" + d)] = function (b) { return e(a, b) }; c[hb("trust_as_" + d)] = function (b) { return g(a, b) } }); return c
            }]
        } function Tf() {
            this.$get = ["$window",
                "$document", function (a, b) {
                    var d = {}, c = !(a.chrome && (a.chrome.app && a.chrome.app.runtime || !a.chrome.app && a.chrome.runtime && a.chrome.runtime.id)) && a.history && a.history.pushState, f = Z((/android (\d+)/.exec(Q((a.navigator || {}).userAgent)) || [])[1]), e = /Boxee/i.test((a.navigator || {}).userAgent), g = b[0] || {}, h, k = /^(Moz|webkit|ms)(?=[A-Z])/, l = g.body && g.body.style, m = !1, n = !1; if (l) {
                        for (var r in l) if (m = k.exec(r)) { h = m[0]; h = h[0].toUpperCase() + h.substr(1); break } h || (h = "WebkitOpacity" in l && "webkit"); m = !!("transition" in l ||
                            h + "Transition" in l); n = !!("animation" in l || h + "Animation" in l); !f || m && n || (m = C(l.webkitTransition), n = C(l.webkitAnimation))
                    } return { history: !(!c || 4 > f || e), hasEvent: function (a) { if ("input" === a && 11 >= Ga) return !1; if (z(d[a])) { var b = g.createElement("div"); d[a] = "on" + a in b } return d[a] }, csp: da(), vendorPrefix: h, transitions: m, animations: n, android: f }
                }]
        } function Vf() {
            var a; this.httpOptions = function (b) { return b ? (a = b, this) : a }; this.$get = ["$templateCache", "$http", "$q", "$sce", function (b, d, c, f) {
                function e(g, h) {
                    e.totalPendingRequests++;
                    if (!C(g) || z(b.get(g))) g = f.getTrustedResourceUrl(g); var k = d.defaults && d.defaults.transformResponse; I(k) ? k = k.filter(function (a) { return a !== hc }) : k === hc && (k = null); return d.get(g, R({ cache: b, transformResponse: k }, a))["finally"](function () { e.totalPendingRequests-- }).then(function (a) { b.put(g, a.data); return a.data }, function (a) { if (!h) throw Mg("tpload", g, a.status, a.statusText); return c.reject(a) })
                } e.totalPendingRequests = 0; return e
            }]
        } function Wf() {
            this.$get = ["$rootScope", "$browser", "$location", function (a, b, d) {
                return {
                    findBindings: function (a,
                        b, d) { a = a.getElementsByClassName("ng-binding"); var g = []; q(a, function (a) { var c = $.element(a).data("$binding"); c && q(c, function (c) { d ? (new RegExp("(^|\\s)" + Ld(b) + "(\\s|\\||$)")).test(c) && g.push(a) : -1 !== c.indexOf(b) && g.push(a) }) }); return g }, findModels: function (a, b, d) { for (var g = ["ng-", "data-ng-", "ng\\:"], h = 0; h < g.length; ++h) { var k = a.querySelectorAll("[" + g[h] + "model" + (d ? "=" : "*=") + '"' + b + '"]'); if (k.length) return k } }, getLocation: function () { return d.url() }, setLocation: function (b) { b !== d.url() && (d.url(b), a.$digest()) },
                    whenStable: function (a) { b.notifyWhenNoOutstandingRequests(a) }
                }
            }]
        } function Xf() {
            this.$get = ["$rootScope", "$browser", "$q", "$$q", "$exceptionHandler", function (a, b, d, c, f) {
                function e(e, k, l) { D(e) || (l = k, k = e, e = y); var m = va.call(arguments, 3), n = x(l) && !l, r = (n ? c : d).defer(), s = r.promise, q; q = b.defer(function () { try { r.resolve(e.apply(null, m)) } catch (b) { r.reject(b), f(b) } finally { delete g[s.$$timeoutId] } n || a.$apply() }, k); s.$$timeoutId = q; g[q] = r; return s } var g = {}; e.cancel = function (a) {
                    return a && a.$$timeoutId in g ? (g[a.$$timeoutId].reject("canceled"),
                        delete g[a.$$timeoutId], b.defer.cancel(a.$$timeoutId)) : !1
                }; return e
            }]
        } function ta(a) { Ga && (aa.setAttribute("href", a), a = aa.href); aa.setAttribute("href", a); return { href: aa.href, protocol: aa.protocol ? aa.protocol.replace(/:$/, "") : "", host: aa.host, search: aa.search ? aa.search.replace(/^\?/, "") : "", hash: aa.hash ? aa.hash.replace(/^#/, "") : "", hostname: aa.hostname, port: aa.port, pathname: "/" === aa.pathname.charAt(0) ? aa.pathname : "/" + aa.pathname } } function qd(a) { a = C(a) ? ta(a) : a; return a.protocol === Nd.protocol && a.host === Nd.host }
    function Yf() { this.$get = ga(w) } function Od(a) { function b(a) { try { return decodeURIComponent(a) } catch (b) { return a } } var d = a[0] || {}, c = {}, f = ""; return function () { var a, g, h, k, l; try { a = d.cookie || "" } catch (m) { a = "" } if (a !== f) for (f = a, a = f.split("; "), c = {}, h = 0; h < a.length; h++)g = a[h], k = g.indexOf("="), 0 < k && (l = b(g.substring(0, k)), z(c[l]) && (c[l] = b(g.substring(k + 1)))); return c } } function bg() { this.$get = Od } function Tc(a) {
        function b(d, c) {
            if (E(d)) { var f = {}; q(d, function (a, c) { f[c] = b(c, a) }); return f } return a.factory(d + "Filter",
                c)
        } this.register = b; this.$get = ["$injector", function (a) { return function (b) { return a.get(b + "Filter") } }]; b("currency", Pd); b("date", Qd); b("filter", Ng); b("json", Og); b("limitTo", Pg); b("lowercase", Qg); b("number", Rd); b("orderBy", Sd); b("uppercase", Rg)
    } function Ng() {
        return function (a, b, d, c) {
            if (!la(a)) { if (null == a) return a; throw G("filter")("notarray", a); } c = c || "$"; var f; switch (pc(b)) { case "function": break; case "boolean": case "null": case "number": case "string": f = !0; case "object": b = Sg(b, d, c, f); break; default: return a }return Array.prototype.filter.call(a,
                b)
        }
    } function Sg(a, b, d, c) { var f = E(a) && d in a; !0 === b ? b = na : D(b) || (b = function (a, b) { if (z(a)) return !1; if (null === a || null === b) return a === b; if (E(b) || E(a) && !Cc(a)) return !1; a = Q("" + a); b = Q("" + b); return -1 !== a.indexOf(b) }); return function (e) { return f && !E(e) ? Na(e, a[d], b, d, !1) : Na(e, a, b, d, c) } } function Na(a, b, d, c, f, e) {
        var g = pc(a), h = pc(b); if ("string" === h && "!" === b.charAt(0)) return !Na(a, b.substring(1), d, c, f); if (I(a)) return a.some(function (a) { return Na(a, b, d, c, f) }); switch (g) {
            case "object": var k; if (f) {
                for (k in a) if ("$" !==
                    k.charAt(0) && Na(a[k], b, d, c, !0)) return !0; return e ? !1 : Na(a, b, d, c, !1)
            } if ("object" === h) { for (k in b) if (e = b[k], !D(e) && !z(e) && (g = k === c, !Na(g ? a : a[k], e, d, c, g, g))) return !1; return !0 } return d(a, b); case "function": return !1; default: return d(a, b)
        }
    } function pc(a) { return null === a ? "null" : typeof a } function Pd(a) { var b = a.NUMBER_FORMATS; return function (a, c, f) { z(c) && (c = b.CURRENCY_SYM); z(f) && (f = b.PATTERNS[1].maxFrac); return null == a ? a : Td(a, b.PATTERNS[1], b.GROUP_SEP, b.DECIMAL_SEP, f).replace(/\u00A4/g, c) } } function Rd(a) {
        var b =
            a.NUMBER_FORMATS; return function (a, c) { return null == a ? a : Td(a, b.PATTERNS[0], b.GROUP_SEP, b.DECIMAL_SEP, c) }
    } function Tg(a) { var b = 0, d, c, f, e, g; -1 < (c = a.indexOf(Ud)) && (a = a.replace(Ud, "")); 0 < (f = a.search(/e/i)) ? (0 > c && (c = f), c += +a.slice(f + 1), a = a.substring(0, f)) : 0 > c && (c = a.length); for (f = 0; a.charAt(f) === qc; f++); if (f === (g = a.length)) d = [0], c = 1; else { for (g--; a.charAt(g) === qc;)g--; c -= f; d = []; for (e = 0; f <= g; f++, e++)d[e] = +a.charAt(f) } c > Vd && (d = d.splice(0, Vd - 1), b = c - 1, c = 1); return { d: d, e: b, i: c } } function Ug(a, b, d, c) {
        var f = a.d, e =
            f.length - a.i; b = z(b) ? Math.min(Math.max(d, e), c) : +b; d = b + a.i; c = f[d]; if (0 < d) { f.splice(Math.max(a.i, d)); for (var g = d; g < f.length; g++)f[g] = 0 } else for (e = Math.max(0, e), a.i = 1, f.length = Math.max(1, d = b + 1), f[0] = 0, g = 1; g < d; g++)f[g] = 0; if (5 <= c) if (0 > d - 1) { for (c = 0; c > d; c--)f.unshift(0), a.i++; f.unshift(1); a.i++ } else f[d - 1]++; for (; e < Math.max(0, b); e++)f.push(0); if (b = f.reduceRight(function (a, b, c, d) { b += a; d[c] = b % 10; return Math.floor(b / 10) }, 0)) f.unshift(b), a.i++
    } function Td(a, b, d, c, f) {
        if (!C(a) && !ba(a) || isNaN(a)) return ""; var e =
            !isFinite(a), g = !1, h = Math.abs(a) + "", k = ""; if (e) k = "\u221e"; else { g = Tg(h); Ug(g, f, b.minFrac, b.maxFrac); k = g.d; h = g.i; f = g.e; e = []; for (g = k.reduce(function (a, b) { return a && !b }, !0); 0 > h;)k.unshift(0), h++; 0 < h ? e = k.splice(h, k.length) : (e = k, k = [0]); h = []; for (k.length >= b.lgSize && h.unshift(k.splice(-b.lgSize, k.length).join("")); k.length > b.gSize;)h.unshift(k.splice(-b.gSize, k.length).join("")); k.length && h.unshift(k.join("")); k = h.join(d); e.length && (k += c + e.join("")); f && (k += "e+" + f) } return 0 > a && !g ? b.negPre + k + b.negSuf : b.posPre +
                k + b.posSuf
    } function Nb(a, b, d, c) { var f = ""; if (0 > a || c && 0 >= a) c ? a = -a + 1 : (a = -a, f = "-"); for (a = "" + a; a.length < b;)a = qc + a; d && (a = a.substr(a.length - b)); return f + a } function V(a, b, d, c, f) { d = d || 0; return function (e) { e = e["get" + a](); if (0 < d || e > -d) e += d; 0 === e && -12 === d && (e = 12); return Nb(e, b, c, f) } } function ob(a, b, d) { return function (c, f) { var e = c["get" + a](), g = xb((d ? "STANDALONE" : "") + (b ? "SHORT" : "") + a); return f[g][e] } } function Wd(a) { var b = (new Date(a, 0, 1)).getDay(); return new Date(a, 0, (4 >= b ? 5 : 12) - b) } function Xd(a) {
        return function (b) {
            var d =
                Wd(b.getFullYear()); b = +new Date(b.getFullYear(), b.getMonth(), b.getDate() + (4 - b.getDay())) - +d; b = 1 + Math.round(b / 6048E5); return Nb(b, a)
        }
    } function rc(a, b) { return 0 >= a.getFullYear() ? b.ERAS[0] : b.ERAS[1] } function Qd(a) {
        function b(a) {
            var b; if (b = a.match(d)) {
                a = new Date(0); var e = 0, g = 0, h = b[8] ? a.setUTCFullYear : a.setFullYear, k = b[8] ? a.setUTCHours : a.setHours; b[9] && (e = Z(b[9] + b[10]), g = Z(b[9] + b[11])); h.call(a, Z(b[1]), Z(b[2]) - 1, Z(b[3])); e = Z(b[4] || 0) - e; g = Z(b[5] || 0) - g; h = Z(b[6] || 0); b = Math.round(1E3 * parseFloat("0." + (b[7] ||
                    0))); k.call(a, e, g, h, b)
            } return a
        } var d = /^(\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?::?(\d\d)(?::?(\d\d)(?:\.(\d+))?)?)?(Z|([+-])(\d\d):?(\d\d))?)?$/; return function (c, d, e) {
            var g = "", h = [], k, l; d = d || "mediumDate"; d = a.DATETIME_FORMATS[d] || d; C(c) && (c = Vg.test(c) ? Z(c) : b(c)); ba(c) && (c = new Date(c)); if (!ja(c) || !isFinite(c.getTime())) return c; for (; d;)(l = Wg.exec(d)) ? (h = cb(h, l, 1), d = h.pop()) : (h.push(d), d = null); var m = c.getTimezoneOffset(); e && (m = Fc(e, m), c = Vb(c, e, !0)); q(h, function (b) {
                k = Xg[b]; g += k ? k(c, a.DATETIME_FORMATS, m) :
                    "''" === b ? "'" : b.replace(/(^'|'$)/g, "").replace(/''/g, "'")
            }); return g
        }
    } function Og() { return function (a, b) { z(b) && (b = 2); return eb(a, b) } } function Pg() { return function (a, b, d) { b = Infinity === Math.abs(Number(b)) ? Number(b) : Z(b); if (ha(b)) return a; ba(a) && (a = a.toString()); if (!la(a)) return a; d = !d || isNaN(d) ? 0 : Z(d); d = 0 > d ? Math.max(0, a.length + d) : d; return 0 <= b ? sc(a, d, d + b) : 0 === d ? sc(a, b, a.length) : sc(a, Math.max(0, d + b), d) } } function sc(a, b, d) { return C(a) ? a.slice(b, d) : va.call(a, b, d) } function Sd(a) {
        function b(b) {
            return b.map(function (b) {
                var c =
                    1, d = $a; if (D(b)) d = b; else if (C(b)) { if ("+" === b.charAt(0) || "-" === b.charAt(0)) c = "-" === b.charAt(0) ? -1 : 1, b = b.substring(1); if ("" !== b && (d = a(b), d.constant)) var f = d(), d = function (a) { return a[f] } } return { get: d, descending: c }
            })
        } function d(a) { switch (typeof a) { case "number": case "boolean": case "string": return !0; default: return !1 } } function c(a, b) {
            var c = 0, d = a.type, k = b.type; if (d === k) {
                var k = a.value, l = b.value; "string" === d ? (k = k.toLowerCase(), l = l.toLowerCase()) : "object" === d && (E(k) && (k = a.index), E(l) && (l = b.index)); k !== l && (c =
                    k < l ? -1 : 1)
            } else c = d < k ? -1 : 1; return c
        } return function (a, e, g, h) {
            if (null == a) return a; if (!la(a)) throw G("orderBy")("notarray", a); I(e) || (e = [e]); 0 === e.length && (e = ["+"]); var k = b(e), l = g ? -1 : 1, m = D(h) ? h : c; a = Array.prototype.map.call(a, function (a, b) {
                return {
                    value: a, tieBreaker: { value: b, type: "number", index: b }, predicateValues: k.map(function (c) {
                        var e = c.get(a); c = typeof e; if (null === e) c = "string", e = "null"; else if ("object" === c) a: { if (D(e.valueOf) && (e = e.valueOf(), d(e))) break a; Cc(e) && (e = e.toString(), d(e)) } return {
                            value: e, type: c,
                            index: b
                        }
                    })
                }
            }); a.sort(function (a, b) { for (var c = 0, d = k.length; c < d; c++) { var e = m(a.predicateValues[c], b.predicateValues[c]); if (e) return e * k[c].descending * l } return m(a.tieBreaker, b.tieBreaker) * l }); return a = a.map(function (a) { return a.value })
        }
    } function Va(a) { D(a) && (a = { link: a }); a.restrict = a.restrict || "AC"; return ga(a) } function Yd(a, b, d, c, f) {
        var e = this, g = []; e.$error = {}; e.$$success = {}; e.$pending = void 0; e.$name = f(b.name || b.ngForm || "")(d); e.$dirty = !1; e.$pristine = !0; e.$valid = !0; e.$invalid = !1; e.$submitted = !1; e.$$parentForm =
            Ob; e.$rollbackViewValue = function () { q(g, function (a) { a.$rollbackViewValue() }) }; e.$commitViewValue = function () { q(g, function (a) { a.$commitViewValue() }) }; e.$addControl = function (a) { Ra(a.$name, "input"); g.push(a); a.$name && (e[a.$name] = a); a.$$parentForm = e }; e.$$renameControl = function (a, b) { var c = a.$name; e[c] === a && delete e[c]; e[b] = a; a.$name = b }; e.$removeControl = function (a) {
                a.$name && e[a.$name] === a && delete e[a.$name]; q(e.$pending, function (b, c) { e.$setValidity(c, null, a) }); q(e.$error, function (b, c) {
                    e.$setValidity(c, null,
                        a)
                }); q(e.$$success, function (b, c) { e.$setValidity(c, null, a) }); bb(g, a); a.$$parentForm = Ob
            }; Zd({ ctrl: this, $element: a, set: function (a, b, c) { var d = a[b]; d ? -1 === d.indexOf(c) && d.push(c) : a[b] = [c] }, unset: function (a, b, c) { var d = a[b]; d && (bb(d, c), 0 === d.length && delete a[b]) }, $animate: c }); e.$setDirty = function () { c.removeClass(a, Wa); c.addClass(a, Pb); e.$dirty = !0; e.$pristine = !1; e.$$parentForm.$setDirty() }; e.$setPristine = function () { c.setClass(a, Wa, Pb + " ng-submitted"); e.$dirty = !1; e.$pristine = !0; e.$submitted = !1; q(g, function (a) { a.$setPristine() }) };
        e.$setUntouched = function () { q(g, function (a) { a.$setUntouched() }) }; e.$setSubmitted = function () { c.addClass(a, "ng-submitted"); e.$submitted = !0; e.$$parentForm.$setSubmitted() }
    } function tc(a) { a.$formatters.push(function (b) { return a.$isEmpty(b) ? b : b.toString() }) } function Xa(a, b, d, c, f, e) {
        var g = Q(b[0].type); if (!f.android) { var h = !1; b.on("compositionstart", function () { h = !0 }); b.on("compositionend", function () { h = !1; l() }) } var k, l = function (a) {
            k && (e.defer.cancel(k), k = null); if (!h) {
                var f = b.val(); a = a && a.type; "password" ===
                    g || d.ngTrim && "false" === d.ngTrim || (f = Y(f)); (c.$viewValue !== f || "" === f && c.$$hasNativeValidators) && c.$setViewValue(f, a)
            }
        }; if (f.hasEvent("input")) b.on("input", l); else { var m = function (a, b, c) { k || (k = e.defer(function () { k = null; b && b.value === c || l(a) })) }; b.on("keydown", function (a) { var b = a.keyCode; 91 === b || 15 < b && 19 > b || 37 <= b && 40 >= b || m(a, this, this.value) }); if (f.hasEvent("paste")) b.on("paste cut", m) } b.on("change", l); if ($d[g] && c.$$hasNativeValidators && g === d.type) b.on("keydown wheel mousedown", function (a) {
            if (!k) {
                var b =
                    this.validity, c = b.badInput, d = b.typeMismatch; k = e.defer(function () { k = null; b.badInput === c && b.typeMismatch === d || l(a) })
            }
        }); c.$render = function () { var a = c.$isEmpty(c.$viewValue) ? "" : c.$viewValue; b.val() !== a && b.val(a) }
    } function Qb(a, b) {
        return function (d, c) {
            var f, e; if (ja(d)) return d; if (C(d)) {
                '"' === d.charAt(0) && '"' === d.charAt(d.length - 1) && (d = d.substring(1, d.length - 1)); if (Yg.test(d)) return new Date(d); a.lastIndex = 0; if (f = a.exec(d)) return f.shift(), e = c ? {
                    yyyy: c.getFullYear(), MM: c.getMonth() + 1, dd: c.getDate(), HH: c.getHours(),
                    mm: c.getMinutes(), ss: c.getSeconds(), sss: c.getMilliseconds() / 1E3
                } : { yyyy: 1970, MM: 1, dd: 1, HH: 0, mm: 0, ss: 0, sss: 0 }, q(f, function (a, c) { c < b.length && (e[b[c]] = +a) }), new Date(e.yyyy, e.MM - 1, e.dd, e.HH, e.mm, e.ss || 0, 1E3 * e.sss || 0)
            } return NaN
        }
    } function pb(a, b, d, c) {
        return function (f, e, g, h, k, l, m) {
            function n(a) { return a && !(a.getTime && a.getTime() !== a.getTime()) } function r(a) { return x(a) && !ja(a) ? d(a) || void 0 : a } uc(f, e, g, h); Xa(f, e, g, h, k, l); var s = h && h.$options && h.$options.timezone, q; h.$$parserName = a; h.$parsers.push(function (a) {
                if (h.$isEmpty(a)) return null;
                if (b.test(a)) return a = d(a, q), s && (a = Vb(a, s)), a
            }); h.$formatters.push(function (a) { if (a && !ja(a)) throw qb("datefmt", a); if (n(a)) return (q = a) && s && (q = Vb(q, s, !0)), m("date")(a, c, s); q = null; return "" }); if (x(g.min) || g.ngMin) { var u; h.$validators.min = function (a) { return !n(a) || z(u) || d(a) >= u }; g.$observe("min", function (a) { u = r(a); h.$validate() }) } if (x(g.max) || g.ngMax) { var p; h.$validators.max = function (a) { return !n(a) || z(p) || d(a) <= p }; g.$observe("max", function (a) { p = r(a); h.$validate() }) }
        }
    } function uc(a, b, d, c) {
        (c.$$hasNativeValidators =
            E(b[0].validity)) && c.$parsers.push(function (a) { var c = b.prop("validity") || {}; return c.badInput || c.typeMismatch ? void 0 : a })
    } function ae(a) { a.$$parserName = "number"; a.$parsers.push(function (b) { if (a.$isEmpty(b)) return null; if (Zg.test(b)) return parseFloat(b) }); a.$formatters.push(function (b) { if (!a.$isEmpty(b)) { if (!ba(b)) throw qb("numfmt", b); b = b.toString() } return b }) } function rb(a) { x(a) && !ba(a) && (a = parseFloat(a)); return ha(a) ? void 0 : a } function vc(a) {
        var b = a.toString(), d = b.indexOf("."); return -1 === d ? -1 < a && 1 >
            a && (a = /e-(\d+)$/.exec(b)) ? Number(a[1]) : 0 : b.length - d - 1
    } function be(a, b, d, c, f) { if (x(c)) { a = a(c); if (!a.constant) throw qb("constexpr", d, c); return a(b) } return f } function wc(a, b) {
        a = "ngClass" + a; return ["$animate", function (d) {
            function c(a, b) { var c = [], d = 0; a: for (; d < a.length; d++) { for (var f = a[d], m = 0; m < b.length; m++)if (f === b[m]) continue a; c.push(f) } return c } function f(a) { var b = []; return I(a) ? (q(a, function (a) { b = b.concat(f(a)) }), b) : C(a) ? a.split(" ") : E(a) ? (q(a, function (a, c) { a && (b = b.concat(c.split(" "))) }), b) : a } return {
                restrict: "AC",
                link: function (e, g, h) {
                    function k(a) { a = l(a, 1); h.$addClass(a) } function l(a, b) { var c = g.data("$classCounts") || W(), d = []; q(a, function (a) { if (0 < b || c[a]) c[a] = (c[a] || 0) + b, c[a] === +(0 < b) && d.push(a) }); g.data("$classCounts", c); return d.join(" ") } function m(a, b) { var e = c(b, a), f = c(a, b), e = l(e, 1), f = l(f, -1); e && e.length && d.addClass(g, e); f && f.length && d.removeClass(g, f) } function n(a) { if (!0 === b || (e.$index & 1) === b) { var c = f(a || []); if (!r) k(c); else if (!na(a, r)) { var d = f(r); m(d, c) } } r = I(a) ? a.map(function (a) { return ka(a) }) : ka(a) }
                    var r; h.$observe("class", function (b) { n(e.$eval(h[a])) }); "ngClass" !== a && e.$watch("$index", function (a, c) { var d = a & 1; if (d !== (c & 1)) { var e = f(r); d === b ? k(e) : (d = l(e, -1), h.$removeClass(d)) } }); e.$watch(h[a], n, !0)
                }
            }
        }]
    } function Zd(a) {
        function b(a, b) { b && !e[a] ? (k.addClass(f, a), e[a] = !0) : !b && e[a] && (k.removeClass(f, a), e[a] = !1) } function d(a, c) { a = a ? "-" + Jc(a, "-") : ""; b(sb + a, !0 === c); b(ce + a, !1 === c) } var c = a.ctrl, f = a.$element, e = {}, g = a.set, h = a.unset, k = a.$animate; e[ce] = !(e[sb] = f.hasClass(sb)); c.$setValidity = function (a, e, f) {
            z(e) ?
            (c.$pending || (c.$pending = {}), g(c.$pending, a, f)) : (c.$pending && h(c.$pending, a, f), de(c.$pending) && (c.$pending = void 0)); Ka(e) ? e ? (h(c.$error, a, f), g(c.$$success, a, f)) : (g(c.$error, a, f), h(c.$$success, a, f)) : (h(c.$error, a, f), h(c.$$success, a, f)); c.$pending ? (b(ee, !0), c.$valid = c.$invalid = void 0, d("", null)) : (b(ee, !1), c.$valid = de(c.$error), c.$invalid = !c.$valid, d("", c.$valid)); e = c.$pending && c.$pending[a] ? void 0 : c.$error[a] ? !1 : c.$$success[a] ? !0 : null; d(a, e); c.$$parentForm.$setValidity(a, e, c)
        }
    } function de(a) {
        if (a) for (var b in a) if (a.hasOwnProperty(b)) return !1;
        return !0
    } var $g = /^\/(.+)\/([a-z]*)$/, ua = Object.prototype.hasOwnProperty, Q = function (a) { return C(a) ? a.toLowerCase() : a }, xb = function (a) { return C(a) ? a.toUpperCase() : a }, Ga, F, za, va = [].slice, ug = [].splice, ah = [].push, ma = Object.prototype.toString, Dc = Object.getPrototypeOf, xa = G("ng"), $ = w.angular || (w.angular = {}), Xb, tb = 0; Ga = w.document.documentMode; var ha = Number.isNaN || function (a) { return a !== a }; y.$inject = []; $a.$inject = []; var I = Array.isArray, re = /^\[object (?:Uint8|Uint8Clamped|Uint16|Uint32|Int8|Int16|Int32|Float32|Float64)Array]$/,
        Y = function (a) { return C(a) ? a.trim() : a }, Ld = function (a) { return a.replace(/([-()[\]{}+?*.$^|,:#<!\\])/g, "\\$1").replace(/\x08/g, "\\x08") }, da = function () {
            if (!x(da.rules)) {
                var a = w.document.querySelector("[ng-csp]") || w.document.querySelector("[data-ng-csp]"); if (a) { var b = a.getAttribute("ng-csp") || a.getAttribute("data-ng-csp"); da.rules = { noUnsafeEval: !b || -1 !== b.indexOf("no-unsafe-eval"), noInlineStyle: !b || -1 !== b.indexOf("no-inline-style") } } else {
                    a = da; try { new Function(""), b = !1 } catch (d) { b = !0 } a.rules = {
                        noUnsafeEval: b,
                        noInlineStyle: !1
                    }
                }
            } return da.rules
        }, vb = function () { if (x(vb.name_)) return vb.name_; var a, b, d = Oa.length, c, f; for (b = 0; b < d; ++b)if (c = Oa[b], a = w.document.querySelector("[" + c.replace(":", "\\:") + "jq]")) { f = a.getAttribute(c + "jq"); break } return vb.name_ = f }, ue = /:/g, Oa = ["ng-", "data-ng-", "ng:", "x-ng-"], xe = function (a) {
            var b = a.currentScript; if (!b) return !0; if (!(b instanceof w.HTMLScriptElement || b instanceof w.SVGScriptElement)) return !1; b = b.attributes; return [b.getNamedItem("src"), b.getNamedItem("href"), b.getNamedItem("xlink:href")].every(function (b) {
                if (!b) return !0;
                if (!b.value) return !1; var c = a.createElement("a"); c.href = b.value; if (a.location.origin === c.origin) return !0; switch (c.protocol) { case "http:": case "https:": case "ftp:": case "blob:": case "file:": case "data:": return !0; default: return !1 }
            })
        }(w.document), Ae = /[A-Z]/g, Kc = !1, De = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:-]+)[^>]*)\/>/gi, La = 3, Ge = { full: "1.5.13", major: 1, minor: 5, dot: 13, codeName: "venerably-shielding" }; S.expando = "ng339"; var kb = S.cache = {}, gg = 1; S._data = function (a) {
            return this.cache[a[this.expando]] ||
                {}
        }; var cg = /([:\-_]+(.))/g, dg = /^moz([A-Z])/, Bb = { mouseleave: "mouseout", mouseenter: "mouseover" }, Zb = G("jqLite"), fg = /^<([\w-]+)\s*\/?>(?:<\/\1>|)$/, Yb = /<|&#?\w+;/, eg = /<([\w:-]+)/, ia = { thead: ["table"], col: ["colgroup", "table"], tr: ["tbody", "table"], td: ["tr", "tbody", "table"] }; ia.tbody = ia.tfoot = ia.colgroup = ia.caption = ia.thead; ia.th = ia.td; var ib = { option: [1, '<select multiple="multiple">', "</select>"], _default: [0, "", ""] }, xc; for (xc in ia) {
            var fe = ia[xc], ge = fe.slice().reverse(); ib[xc] = [ge.length, "<" + ge.join("><") +
                ">", "</" + fe.join("></") + ">"]
        } ib.optgroup = ib.option; var lg = w.Node.prototype.contains || function (a) { return !!(this.compareDocumentPosition(a) & 16) }, Pa = S.prototype = { ready: function (a) { function b() { d || (d = !0, a()) } var d = !1; "complete" === w.document.readyState ? w.setTimeout(b) : (this.on("DOMContentLoaded", b), S(w).on("load", b)) }, toString: function () { var a = []; q(this, function (b) { a.push("" + b) }); return "[" + a.join(", ") + "]" }, eq: function (a) { return 0 <= a ? F(this[a]) : F(this[this.length + a]) }, length: 0, push: ah, sort: [].sort, splice: [].splice },
            Hb = {}; q("multiple selected checked disabled readOnly required open".split(" "), function (a) { Hb[Q(a)] = a }); var bd = {}; q("input select option textarea button form details".split(" "), function (a) { bd[a] = !0 }); var id = { ngMinlength: "minlength", ngMaxlength: "maxlength", ngMin: "min", ngMax: "max", ngPattern: "pattern" }; q({ data: ac, removeData: jb, hasData: function (a) { for (var b in kb[a.ng339]) return !0; return !1 }, cleanData: function (a) { for (var b = 0, d = a.length; b < d; b++)jb(a[b]) }, htmlPrefilter: function (a) { return a } }, function (a,
                b) { S[b] = a }); q({
                    data: ac, inheritedData: Fb, scope: function (a) { return F.data(a, "$scope") || Fb(a.parentNode || a, ["$isolateScope", "$scope"]) }, isolateScope: function (a) { return F.data(a, "$isolateScope") || F.data(a, "$isolateScopeNoTemplate") }, controller: Zc, injector: function (a) { return Fb(a, "$injector") }, removeAttr: function (a, b) { a.removeAttribute(b) }, hasClass: Cb, css: function (a, b, d) { b = hb(b); if (x(d)) a.style[b] = d; else return a.style[b] }, attr: function (a, b, d) {
                        var c = a.nodeType; if (c !== La && 2 !== c && 8 !== c) if (c = Q(b), Hb[c]) if (x(d)) d ?
                            (a[b] = !0, a.setAttribute(b, c)) : (a[b] = !1, a.removeAttribute(c)); else return a[b] || (a.attributes.getNamedItem(b) || y).specified ? c : void 0; else if (x(d)) a.setAttribute(b, d); else if (a.getAttribute) return a = a.getAttribute(b, 2), null === a ? void 0 : a
                    }, prop: function (a, b, d) { if (x(d)) a[b] = d; else return a[b] }, text: function () { function a(a, d) { if (z(d)) { var c = a.nodeType; return 1 === c || c === La ? a.textContent : "" } a.textContent = d } a.$dv = ""; return a }(), val: function (a, b) {
                        if (z(b)) {
                            if (a.multiple && "select" === wa(a)) {
                                var d = []; q(a.options,
                                    function (a) { a.selected && d.push(a.value || a.text) }); return 0 === d.length ? null : d
                            } return a.value
                        } a.value = b
                    }, html: function (a, b) { if (z(b)) return a.innerHTML; zb(a, !0); a.innerHTML = b }, empty: $c
                }, function (a, b) {
                    S.prototype[b] = function (b, c) {
                        var f, e, g = this.length; if (a !== $c && z(2 === a.length && a !== Cb && a !== Zc ? b : c)) { if (E(b)) { for (f = 0; f < g; f++)if (a === ac) a(this[f], b); else for (e in b) a(this[f], e, b[e]); return this } f = a.$dv; g = z(f) ? Math.min(g, 1) : g; for (e = 0; e < g; e++) { var h = a(this[e], b, c); f = f ? f + h : h } return f } for (f = 0; f < g; f++)a(this[f],
                            b, c); return this
                    }
                }); q({
                    removeData: jb, on: function (a, b, d, c) { if (x(c)) throw Zb("onargs"); if (Uc(a)) { c = Ab(a, !0); var f = c.events, e = c.handle; e || (e = c.handle = ig(a, f)); c = 0 <= b.indexOf(" ") ? b.split(" ") : [b]; for (var g = c.length, h = function (b, c, g) { var h = f[b]; h || (h = f[b] = [], h.specialHandlerWrapper = c, "$destroy" === b || g || a.addEventListener(b, e, !1)); h.push(d) }; g--;)b = c[g], Bb[b] ? (h(Bb[b], kg), h(b, void 0, !0)) : h(b) } }, off: Yc, one: function (a, b, d) { a = F(a); a.on(b, function f() { a.off(b, d); a.off(b, f) }); a.on(b, d) }, replaceWith: function (a,
                        b) { var d, c = a.parentNode; zb(a); q(new S(b), function (b) { d ? c.insertBefore(b, d.nextSibling) : c.replaceChild(b, a); d = b }) }, children: function (a) { var b = []; q(a.childNodes, function (a) { 1 === a.nodeType && b.push(a) }); return b }, contents: function (a) { return a.contentDocument || a.childNodes || [] }, append: function (a, b) { var d = a.nodeType; if (1 === d || 11 === d) { b = new S(b); for (var d = 0, c = b.length; d < c; d++)a.appendChild(b[d]) } }, prepend: function (a, b) { if (1 === a.nodeType) { var d = a.firstChild; q(new S(b), function (b) { a.insertBefore(b, d) }) } },
                    wrap: function (a, b) { Wc(a, F(b).eq(0).clone()[0]) }, remove: Gb, detach: function (a) { Gb(a, !0) }, after: function (a, b) { var d = a, c = a.parentNode; if (c) { b = new S(b); for (var f = 0, e = b.length; f < e; f++) { var g = b[f]; c.insertBefore(g, d.nextSibling); d = g } } }, addClass: Eb, removeClass: Db, toggleClass: function (a, b, d) { b && q(b.split(" "), function (b) { var f = d; z(f) && (f = !Cb(a, b)); (f ? Eb : Db)(a, b) }) }, parent: function (a) { return (a = a.parentNode) && 11 !== a.nodeType ? a : null }, next: function (a) { return a.nextElementSibling }, find: function (a, b) {
                        return a.getElementsByTagName ?
                            a.getElementsByTagName(b) : []
                    }, clone: $b, triggerHandler: function (a, b, d) {
                        var c, f, e = b.type || b, g = Ab(a); if (g = (g = g && g.events) && g[e]) c = { preventDefault: function () { this.defaultPrevented = !0 }, isDefaultPrevented: function () { return !0 === this.defaultPrevented }, stopImmediatePropagation: function () { this.immediatePropagationStopped = !0 }, isImmediatePropagationStopped: function () { return !0 === this.immediatePropagationStopped }, stopPropagation: y, type: e, target: a }, b.type && (c = R(c, b)), b = ka(g), f = d ? [c].concat(d) : [c], q(b, function (b) {
                            c.isImmediatePropagationStopped() ||
                            b.apply(a, f)
                        })
                    }
                }, function (a, b) { S.prototype[b] = function (b, c, f) { for (var e, g = 0, h = this.length; g < h; g++)z(e) ? (e = a(this[g], b, c, f), x(e) && (e = F(e))) : Xc(e, a(this[g], b, c, f)); return x(e) ? e : this } }); S.prototype.bind = S.prototype.on; S.prototype.unbind = S.prototype.off; Sa.prototype = { put: function (a, b) { this[Aa(a, this.nextUid)] = b }, get: function (a) { return this[Aa(a, this.nextUid)] }, remove: function (a) { var b = this[a = Aa(a, this.nextUid)]; delete this[a]; return b } }; var ag = [function () { this.$get = [function () { return Sa }] }], ng = /^([^(]+?)=>/,
                    og = /^[^(]*\(\s*([^)]*)\)/m, bh = /,/, ch = /^\s*(_?)(\S+?)\1\s*$/, mg = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg, Ba = G("$injector"); fb.$$annotate = function (a, b, d) { var c; if ("function" === typeof a) { if (!(c = a.$inject)) { c = []; if (a.length) { if (b) throw C(d) && d || (d = a.name || pg(a)), Ba("strictdi", d); b = cd(a); q(b[1].split(bh), function (a) { a.replace(ch, function (a, b, d) { c.push(d) }) }) } a.$inject = c } } else I(a) ? (b = a.length - 1, Qa(a[b], "fn"), c = a.slice(0, b)) : Qa(a, "fn", !0); return c }; var he = G("$animate"), tf = function () { this.$get = y }, uf = function () {
                        var a =
                            new Sa, b = []; this.$get = ["$$AnimateRunner", "$rootScope", function (d, c) {
                                function f(a, b, c) { var d = !1; b && (b = C(b) ? b.split(" ") : I(b) ? b : [], q(b, function (b) { b && (d = !0, a[b] = c) })); return d } function e() { q(b, function (b) { var c = a.get(b); if (c) { var d = qg(b.attr("class")), e = "", f = ""; q(c, function (a, b) { a !== !!d[b] && (a ? e += (e.length ? " " : "") + b : f += (f.length ? " " : "") + b) }); q(b, function (a) { e && Eb(a, e); f && Db(a, f) }); a.remove(b) } }); b.length = 0 } return {
                                    enabled: y, on: y, off: y, pin: y, push: function (g, h, k, l) {
                                        l && l(); k = k || {}; k.from && g.css(k.from);
                                        k.to && g.css(k.to); if (k.addClass || k.removeClass) if (h = k.addClass, l = k.removeClass, k = a.get(g) || {}, h = f(k, h, !0), l = f(k, l, !1), h || l) a.put(g, k), b.push(g), 1 === b.length && c.$$postDigest(e); g = new d; g.complete(); return g
                                    }
                                }
                            }]
                    }, rf = ["$provide", function (a) {
                        var b = this; this.$$registeredAnimations = Object.create(null); this.register = function (d, c) { if (d && "." !== d.charAt(0)) throw he("notcsel", d); var f = d + "-animation"; b.$$registeredAnimations[d.substr(1)] = f; a.factory(f, c) }; this.classNameFilter = function (a) {
                            if (1 === arguments.length &&
                                (this.$$classNameFilter = a instanceof RegExp ? a : null) && /(\s+|\/)ng-animate(\s+|\/)/.test(this.$$classNameFilter.toString())) throw he("nongcls", "ng-animate"); return this.$$classNameFilter
                        }; this.$get = ["$$animateQueue", function (a) {
                            function b(a, c, d) { if (d) { var h; a: { for (h = 0; h < d.length; h++) { var k = d[h]; if (1 === k.nodeType) { h = k; break a } } h = void 0 } !h || h.parentNode || h.previousElementSibling || (d = null) } d ? d.after(a) : c.prepend(a) } return {
                                on: a.on, off: a.off, pin: a.pin, enabled: a.enabled, cancel: function (a) { a.end && a.end() },
                                enter: function (f, e, g, h) { e = e && F(e); g = g && F(g); e = e || g.parent(); b(f, e, g); return a.push(f, "enter", Ca(h)) }, move: function (f, e, g, h) { e = e && F(e); g = g && F(g); e = e || g.parent(); b(f, e, g); return a.push(f, "move", Ca(h)) }, leave: function (b, c) { return a.push(b, "leave", Ca(c), function () { b.remove() }) }, addClass: function (b, c, g) { g = Ca(g); g.addClass = lb(g.addclass, c); return a.push(b, "addClass", g) }, removeClass: function (b, c, g) { g = Ca(g); g.removeClass = lb(g.removeClass, c); return a.push(b, "removeClass", g) }, setClass: function (b, c, g, h) {
                                    h = Ca(h);
                                    h.addClass = lb(h.addClass, c); h.removeClass = lb(h.removeClass, g); return a.push(b, "setClass", h)
                                }, animate: function (b, c, g, h, k) { k = Ca(k); k.from = k.from ? R(k.from, c) : c; k.to = k.to ? R(k.to, g) : g; k.tempClasses = lb(k.tempClasses, h || "ng-inline-animate"); return a.push(b, "animate", k) }
                            }
                        }]
                    }], wf = function () { this.$get = ["$$rAF", function (a) { function b(b) { d.push(b); 1 < d.length || a(function () { for (var a = 0; a < d.length; a++)d[a](); d = [] }) } var d = []; return function () { var a = !1; b(function () { a = !0 }); return function (d) { a ? d() : b(d) } } }] }, vf = function () {
                        this.$get =
                        ["$q", "$sniffer", "$$animateAsyncRun", "$document", "$timeout", function (a, b, d, c, f) {
                            function e(a) { this.setHost(a); var b = d(); this._doneCallbacks = []; this._tick = function (a) { var d = c[0]; d && d.hidden ? f(a, 0, !1) : b(a) }; this._state = 0 } e.chain = function (a, b) { function c() { if (d === a.length) b(!0); else a[d](function (a) { !1 === a ? b(!1) : (d++, c()) }) } var d = 0; c() }; e.all = function (a, b) { function c(f) { e = e && f; ++d === a.length && b(e) } var d = 0, e = !0; q(a, function (a) { a.done(c) }) }; e.prototype = {
                                setHost: function (a) { this.host = a || {} }, done: function (a) {
                                    2 ===
                                    this._state ? a() : this._doneCallbacks.push(a)
                                }, progress: y, getPromise: function () { if (!this.promise) { var b = this; this.promise = a(function (a, c) { b.done(function (b) { !1 === b ? c() : a() }) }) } return this.promise }, then: function (a, b) { return this.getPromise().then(a, b) }, "catch": function (a) { return this.getPromise()["catch"](a) }, "finally": function (a) { return this.getPromise()["finally"](a) }, pause: function () { this.host.pause && this.host.pause() }, resume: function () { this.host.resume && this.host.resume() }, end: function () {
                                    this.host.end &&
                                    this.host.end(); this._resolve(!0)
                                }, cancel: function () { this.host.cancel && this.host.cancel(); this._resolve(!1) }, complete: function (a) { var b = this; 0 === b._state && (b._state = 1, b._tick(function () { b._resolve(a) })) }, _resolve: function (a) { 2 !== this._state && (q(this._doneCallbacks, function (b) { b(a) }), this._doneCallbacks.length = 0, this._state = 2) }
                            }; return e
                        }]
                    }, sf = function () {
                        this.$get = ["$$rAF", "$q", "$$AnimateRunner", function (a, b, d) {
                            return function (b, f) {
                                function e() {
                                    a(function () {
                                        g.addClass && (b.addClass(g.addClass), g.addClass =
                                            null); g.removeClass && (b.removeClass(g.removeClass), g.removeClass = null); g.to && (b.css(g.to), g.to = null); h || k.complete(); h = !0
                                    }); return k
                                } var g = f || {}; g.$$prepared || (g = sa(g)); g.cleanupStyles && (g.from = g.to = null); g.from && (b.css(g.from), g.from = null); var h, k = new d; return { start: e, end: e }
                            }
                        }]
                    }, fa = G("$compile"), fc = new function () { }; Mc.$inject = ["$provide", "$$sanitizeUriProvider"]; Ib.prototype.isFirstChange = function () { return this.previousValue === fc }; var dd = /^((?:x|data)[:\-_])/i, kd = G("$controller"), jd = /^(\S+)(\s+as\s+([\w$]+))?$/,
                        Cf = function () { this.$get = ["$document", function (a) { return function (b) { b ? !b.nodeType && b instanceof F && (b = b[0]) : b = a[0].body; return b.offsetWidth + 1 } }] }, ld = "application/json", ic = { "Content-Type": ld + ";charset=utf-8" }, wg = /^\[|^\{(?!\{)/, xg = { "[": /]$/, "{": /}$/ }, vg = /^\)]\}',?\n/, dh = G("$http"), pd = function (a) { return function () { throw dh("legacy", a); } }, Ia = $.$interpolateMinErr = G("$interpolate"); Ia.throwNoconcat = function (a) { throw Ia("noconcat", a); }; Ia.interr = function (a, b) { return Ia("interr", a, b.toString()) }; var Kf =
                            function () { this.$get = ["$window", function (a) { function b(a) { var b = function (a) { b.data = a; b.called = !0 }; b.id = a; return b } var d = a.angular.callbacks, c = {}; return { createCallback: function (a) { a = "_" + (d.$$counter++).toString(36); var e = "angular.callbacks." + a, g = b(a); c[e] = d[a] = g; return e }, wasCalled: function (a) { return c[a].called }, getResponse: function (a) { return c[a].data }, removeCallback: function (a) { delete d[c[a].id]; delete c[a] } } }] }, eh = /^([^?#]*)(\?([^#]*))?(#(.*))?$/, zg = { http: 80, https: 443, ftp: 21 }, mb = G("$location"),
                            Ag = /^\s*[\\/]{2,}/, fh = {
                                $$absUrl: "", $$html5: !1, $$replace: !1, absUrl: Jb("$$absUrl"), url: function (a) { if (z(a)) return this.$$url; var b = eh.exec(a); (b[1] || "" === a) && this.path(decodeURIComponent(b[1])); (b[2] || b[1] || "" === a) && this.search(b[3] || ""); this.hash(b[5] || ""); return this }, protocol: Jb("$$protocol"), host: Jb("$$host"), port: Jb("$$port"), path: ud("$$path", function (a) { a = null !== a ? a.toString() : ""; return "/" === a.charAt(0) ? a : "/" + a }), search: function (a, b) {
                                    switch (arguments.length) {
                                        case 0: return this.$$search; case 1: if (C(a) ||
                                            ba(a)) a = a.toString(), this.$$search = Hc(a); else if (E(a)) a = sa(a, {}), q(a, function (b, c) { null == b && delete a[c] }), this.$$search = a; else throw mb("isrcharg"); break; default: z(b) || null === b ? delete this.$$search[a] : this.$$search[a] = b
                                    }this.$$compose(); return this
                                }, hash: ud("$$hash", function (a) { return null !== a ? a.toString() : "" }), replace: function () { this.$$replace = !0; return this }
                            }; q([td, lc, kc], function (a) {
                                a.prototype = Object.create(fh); a.prototype.state = function (b) {
                                    if (!arguments.length) return this.$$state; if (a !== kc ||
                                        !this.$$html5) throw mb("nostate"); this.$$state = z(b) ? null : b; return this
                                }
                            }); var ea = G("$parse"), wd = [].constructor, xd = (!1).constructor, yd = Function.constructor, zd = (0).constructor, Ad = {}.constructor, Bd = "".constructor, Fg = wd.prototype, Gg = xd.prototype, Lb = yd.prototype, Hg = zd.prototype, Cd = Ad.prototype, Ig = Bd.prototype, Cg = Lb.call, Dg = Lb.apply, Eg = Lb.bind, Kg = Cd.valueOf, Rb = W(); q("+ - * / % === !== == != < > <= >= && || ! = |".split(" "), function (a) { Rb[a] = !0 }); var gh = { n: "\n", f: "\f", r: "\r", t: "\t", v: "\v", "'": "'", '"': '"' },
                                nc = function (a) { this.options = a }; nc.prototype = {
                                    constructor: nc, lex: function (a) {
                                        this.text = a; this.index = 0; for (this.tokens = []; this.index < this.text.length;)if (a = this.text.charAt(this.index), '"' === a || "'" === a) this.readString(a); else if (this.isNumber(a) || "." === a && this.isNumber(this.peek())) this.readNumber(); else if (this.isIdentifierStart(this.peekMultichar())) this.readIdent(); else if (this.is(a, "(){}[].,;:?")) this.tokens.push({ index: this.index, text: a }), this.index++; else if (this.isWhitespace(a)) this.index++;
                                        else { var b = a + this.peek(), d = b + this.peek(2), c = Rb[b], f = Rb[d]; Rb[a] || c || f ? (a = f ? d : c ? b : a, this.tokens.push({ index: this.index, text: a, operator: !0 }), this.index += a.length) : this.throwError("Unexpected next character ", this.index, this.index + 1) } return this.tokens
                                    }, is: function (a, b) { return -1 !== b.indexOf(a) }, peek: function (a) { a = a || 1; return this.index + a < this.text.length ? this.text.charAt(this.index + a) : !1 }, isNumber: function (a) { return "0" <= a && "9" >= a && "string" === typeof a }, isWhitespace: function (a) {
                                        return " " === a || "\r" === a ||
                                            "\t" === a || "\n" === a || "\v" === a || "\u00a0" === a
                                    }, isIdentifierStart: function (a) { return this.options.isIdentifierStart ? this.options.isIdentifierStart(a, this.codePointAt(a)) : this.isValidIdentifierStart(a) }, isValidIdentifierStart: function (a) { return "a" <= a && "z" >= a || "A" <= a && "Z" >= a || "_" === a || "$" === a }, isIdentifierContinue: function (a) { return this.options.isIdentifierContinue ? this.options.isIdentifierContinue(a, this.codePointAt(a)) : this.isValidIdentifierContinue(a) }, isValidIdentifierContinue: function (a, b) {
                                        return this.isValidIdentifierStart(a,
                                            b) || this.isNumber(a)
                                    }, codePointAt: function (a) { return 1 === a.length ? a.charCodeAt(0) : (a.charCodeAt(0) << 10) + a.charCodeAt(1) - 56613888 }, peekMultichar: function () { var a = this.text.charAt(this.index), b = this.peek(); if (!b) return a; var d = a.charCodeAt(0), c = b.charCodeAt(0); return 55296 <= d && 56319 >= d && 56320 <= c && 57343 >= c ? a + b : a }, isExpOperator: function (a) { return "-" === a || "+" === a || this.isNumber(a) }, throwError: function (a, b, d) {
                                        d = d || this.index; b = x(b) ? "s " + b + "-" + this.index + " [" + this.text.substring(b, d) + "]" : " " + d; throw ea("lexerr",
                                            a, b, this.text);
                                    }, readNumber: function () { for (var a = "", b = this.index; this.index < this.text.length;) { var d = Q(this.text.charAt(this.index)); if ("." === d || this.isNumber(d)) a += d; else { var c = this.peek(); if ("e" === d && this.isExpOperator(c)) a += d; else if (this.isExpOperator(d) && c && this.isNumber(c) && "e" === a.charAt(a.length - 1)) a += d; else if (!this.isExpOperator(d) || c && this.isNumber(c) || "e" !== a.charAt(a.length - 1)) break; else this.throwError("Invalid exponent") } this.index++ } this.tokens.push({ index: b, text: a, constant: !0, value: Number(a) }) },
                                    readIdent: function () { var a = this.index; for (this.index += this.peekMultichar().length; this.index < this.text.length;) { var b = this.peekMultichar(); if (!this.isIdentifierContinue(b)) break; this.index += b.length } this.tokens.push({ index: a, text: this.text.slice(a, this.index), identifier: !0 }) }, readString: function (a) {
                                        var b = this.index; this.index++; for (var d = "", c = a, f = !1; this.index < this.text.length;) {
                                            var e = this.text.charAt(this.index), c = c + e; if (f) "u" === e ? (f = this.text.substring(this.index + 1, this.index + 5), f.match(/[\da-f]{4}/i) ||
                                                this.throwError("Invalid unicode escape [\\u" + f + "]"), this.index += 4, d += String.fromCharCode(parseInt(f, 16))) : d += gh[e] || e, f = !1; else if ("\\" === e) f = !0; else { if (e === a) { this.index++; this.tokens.push({ index: b, text: c, constant: !0, value: d }); return } d += e } this.index++
                                        } this.throwError("Unterminated quote", b)
                                    }
                                }; var t = function (a, b) { this.lexer = a; this.options = b }; t.Program = "Program"; t.ExpressionStatement = "ExpressionStatement"; t.AssignmentExpression = "AssignmentExpression"; t.ConditionalExpression = "ConditionalExpression";
    t.LogicalExpression = "LogicalExpression"; t.BinaryExpression = "BinaryExpression"; t.UnaryExpression = "UnaryExpression"; t.CallExpression = "CallExpression"; t.MemberExpression = "MemberExpression"; t.Identifier = "Identifier"; t.Literal = "Literal"; t.ArrayExpression = "ArrayExpression"; t.Property = "Property"; t.ObjectExpression = "ObjectExpression"; t.ThisExpression = "ThisExpression"; t.LocalsExpression = "LocalsExpression"; t.NGValueParameter = "NGValueParameter"; t.prototype = {
        ast: function (a) {
            this.text = a; this.tokens = this.lexer.lex(a);
            a = this.program(); 0 !== this.tokens.length && this.throwError("is an unexpected token", this.tokens[0]); return a
        }, program: function () { for (var a = []; ;)if (0 < this.tokens.length && !this.peek("}", ")", ";", "]") && a.push(this.expressionStatement()), !this.expect(";")) return { type: t.Program, body: a } }, expressionStatement: function () { return { type: t.ExpressionStatement, expression: this.filterChain() } }, filterChain: function () { for (var a = this.expression(); this.expect("|");)a = this.filter(a); return a }, expression: function () { return this.assignment() },
        assignment: function () { var a = this.ternary(); if (this.expect("=")) { if (!Fd(a)) throw ea("lval"); a = { type: t.AssignmentExpression, left: a, right: this.assignment(), operator: "=" } } return a }, ternary: function () { var a = this.logicalOR(), b, d; return this.expect("?") && (b = this.expression(), this.consume(":")) ? (d = this.expression(), { type: t.ConditionalExpression, test: a, alternate: b, consequent: d }) : a }, logicalOR: function () {
            for (var a = this.logicalAND(); this.expect("||");)a = { type: t.LogicalExpression, operator: "||", left: a, right: this.logicalAND() };
            return a
        }, logicalAND: function () { for (var a = this.equality(); this.expect("&&");)a = { type: t.LogicalExpression, operator: "&&", left: a, right: this.equality() }; return a }, equality: function () { for (var a = this.relational(), b; b = this.expect("==", "!=", "===", "!==");)a = { type: t.BinaryExpression, operator: b.text, left: a, right: this.relational() }; return a }, relational: function () { for (var a = this.additive(), b; b = this.expect("<", ">", "<=", ">=");)a = { type: t.BinaryExpression, operator: b.text, left: a, right: this.additive() }; return a }, additive: function () {
            for (var a =
                this.multiplicative(), b; b = this.expect("+", "-");)a = { type: t.BinaryExpression, operator: b.text, left: a, right: this.multiplicative() }; return a
        }, multiplicative: function () { for (var a = this.unary(), b; b = this.expect("*", "/", "%");)a = { type: t.BinaryExpression, operator: b.text, left: a, right: this.unary() }; return a }, unary: function () { var a; return (a = this.expect("+", "-", "!")) ? { type: t.UnaryExpression, operator: a.text, prefix: !0, argument: this.unary() } : this.primary() }, primary: function () {
            var a; this.expect("(") ? (a = this.filterChain(),
                this.consume(")")) : this.expect("[") ? a = this.arrayDeclaration() : this.expect("{") ? a = this.object() : this.selfReferential.hasOwnProperty(this.peek().text) ? a = sa(this.selfReferential[this.consume().text]) : this.options.literals.hasOwnProperty(this.peek().text) ? a = { type: t.Literal, value: this.options.literals[this.consume().text] } : this.peek().identifier ? a = this.identifier() : this.peek().constant ? a = this.constant() : this.throwError("not a primary expression", this.peek()); for (var b; b = this.expect("(", "[", ".");)"(" ===
                    b.text ? (a = { type: t.CallExpression, callee: a, arguments: this.parseArguments() }, this.consume(")")) : "[" === b.text ? (a = { type: t.MemberExpression, object: a, property: this.expression(), computed: !0 }, this.consume("]")) : "." === b.text ? a = { type: t.MemberExpression, object: a, property: this.identifier(), computed: !1 } : this.throwError("IMPOSSIBLE"); return a
        }, filter: function (a) { a = [a]; for (var b = { type: t.CallExpression, callee: this.identifier(), arguments: a, filter: !0 }; this.expect(":");)a.push(this.expression()); return b }, parseArguments: function () {
            var a =
                []; if (")" !== this.peekToken().text) { do a.push(this.filterChain()); while (this.expect(",")) } return a
        }, identifier: function () { var a = this.consume(); a.identifier || this.throwError("is not a valid identifier", a); return { type: t.Identifier, name: a.text } }, constant: function () { return { type: t.Literal, value: this.consume().value } }, arrayDeclaration: function () {
            var a = []; if ("]" !== this.peekToken().text) { do { if (this.peek("]")) break; a.push(this.expression()) } while (this.expect(",")) } this.consume("]"); return {
                type: t.ArrayExpression,
                elements: a
            }
        }, object: function () {
            var a = [], b; if ("}" !== this.peekToken().text) {
                do {
                    if (this.peek("}")) break; b = { type: t.Property, kind: "init" }; this.peek().constant ? (b.key = this.constant(), b.computed = !1, this.consume(":"), b.value = this.expression()) : this.peek().identifier ? (b.key = this.identifier(), b.computed = !1, this.peek(":") ? (this.consume(":"), b.value = this.expression()) : b.value = b.key) : this.peek("[") ? (this.consume("["), b.key = this.expression(), this.consume("]"), b.computed = !0, this.consume(":"), b.value = this.expression()) :
                        this.throwError("invalid key", this.peek()); a.push(b)
                } while (this.expect(","))
            } this.consume("}"); return { type: t.ObjectExpression, properties: a }
        }, throwError: function (a, b) { throw ea("syntax", b.text, a, b.index + 1, this.text, this.text.substring(b.index)); }, consume: function (a) { if (0 === this.tokens.length) throw ea("ueoe", this.text); var b = this.expect(a); b || this.throwError("is unexpected, expecting [" + a + "]", this.peek()); return b }, peekToken: function () { if (0 === this.tokens.length) throw ea("ueoe", this.text); return this.tokens[0] },
        peek: function (a, b, d, c) { return this.peekAhead(0, a, b, d, c) }, peekAhead: function (a, b, d, c, f) { if (this.tokens.length > a) { a = this.tokens[a]; var e = a.text; if (e === b || e === d || e === c || e === f || !(b || d || c || f)) return a } return !1 }, expect: function (a, b, d, c) { return (a = this.peek(a, b, d, c)) ? (this.tokens.shift(), a) : !1 }, selfReferential: { "this": { type: t.ThisExpression }, $locals: { type: t.LocalsExpression } }
    }; Id.prototype = {
        compile: function (a, b) {
            var d = this, c = this.astBuilder.ast(a); this.state = {
                nextId: 0, filters: {}, expensiveChecks: b, fn: {
                    vars: [],
                    body: [], own: {}
                }, assign: { vars: [], body: [], own: {} }, inputs: []
            }; X(c, d.$filter); var f = "", e; this.stage = "assign"; if (e = Gd(c)) this.state.computing = "assign", f = this.nextId(), this.recurse(e, f), this.return_(f), f = "fn.assign=" + this.generateFunction("assign", "s,v,l"); e = Ed(c.body); d.stage = "inputs"; q(e, function (a, b) { var c = "fn" + b; d.state[c] = { vars: [], body: [], own: {} }; d.state.computing = c; var e = d.nextId(); d.recurse(a, e); d.return_(e); d.state.inputs.push(c); a.watchId = b }); this.state.computing = "fn"; this.stage = "main"; this.recurse(c);
            f = '"' + this.USE + " " + this.STRICT + '";\n' + this.filterPrefix() + "var fn=" + this.generateFunction("fn", "s,l,a,i") + f + this.watchFns() + "return fn;"; f = (new Function("$filter", "ensureSafeMemberName", "ensureSafeObject", "ensureSafeFunction", "getStringValue", "ensureSafeAssignContext", "ifDefined", "plus", "text", f))(this.$filter, Ua, Ea, vd, Bg, Kb, Jg, Dd, a); this.state = this.stage = void 0; f.literal = Hd(c); f.constant = c.constant; return f
        }, USE: "use", STRICT: "strict", watchFns: function () {
            var a = [], b = this.state.inputs, d = this; q(b, function (b) {
                a.push("var " +
                    b + "=" + d.generateFunction(b, "s"))
            }); b.length && a.push("fn.inputs=[" + b.join(",") + "];"); return a.join("")
        }, generateFunction: function (a, b) { return "function(" + b + "){" + this.varsPrefix(a) + this.body(a) + "};" }, filterPrefix: function () { var a = [], b = this; q(this.state.filters, function (d, c) { a.push(d + "=$filter(" + b.escape(c) + ")") }); return a.length ? "var " + a.join(",") + ";" : "" }, varsPrefix: function (a) { return this.state[a].vars.length ? "var " + this.state[a].vars.join(",") + ";" : "" }, body: function (a) { return this.state[a].body.join("") },
        recurse: function (a, b, d, c, f, e) {
            var g, h, k = this, l, m, n; c = c || y; if (!e && x(a.watchId)) b = b || this.nextId(), this.if_("i", this.lazyAssign(b, this.computedMember("i", a.watchId)), this.lazyRecurse(a, b, d, c, f, !0)); else switch (a.type) {
                case t.Program: q(a.body, function (b, c) { k.recurse(b.expression, void 0, void 0, function (a) { h = a }); c !== a.body.length - 1 ? k.current().body.push(h, ";") : k.return_(h) }); break; case t.Literal: m = this.escape(a.value); this.assign(b, m); c(m); break; case t.UnaryExpression: this.recurse(a.argument, void 0, void 0,
                    function (a) { h = a }); m = a.operator + "(" + this.ifDefined(h, 0) + ")"; this.assign(b, m); c(m); break; case t.BinaryExpression: this.recurse(a.left, void 0, void 0, function (a) { g = a }); this.recurse(a.right, void 0, void 0, function (a) { h = a }); m = "+" === a.operator ? this.plus(g, h) : "-" === a.operator ? this.ifDefined(g, 0) + a.operator + this.ifDefined(h, 0) : "(" + g + ")" + a.operator + "(" + h + ")"; this.assign(b, m); c(m); break; case t.LogicalExpression: b = b || this.nextId(); k.recurse(a.left, b); k.if_("&&" === a.operator ? b : k.not(b), k.lazyRecurse(a.right, b));
                    c(b); break; case t.ConditionalExpression: b = b || this.nextId(); k.recurse(a.test, b); k.if_(b, k.lazyRecurse(a.alternate, b), k.lazyRecurse(a.consequent, b)); c(b); break; case t.Identifier: b = b || this.nextId(); d && (d.context = "inputs" === k.stage ? "s" : this.assign(this.nextId(), this.getHasOwnProperty("l", a.name) + "?l:s"), d.computed = !1, d.name = a.name); Ua(a.name); k.if_("inputs" === k.stage || k.not(k.getHasOwnProperty("l", a.name)), function () {
                        k.if_("inputs" === k.stage || "s", function () {
                            f && 1 !== f && k.if_(k.not(k.nonComputedMember("s",
                                a.name)), k.lazyAssign(k.nonComputedMember("s", a.name), "{}")); k.assign(b, k.nonComputedMember("s", a.name))
                        })
                    }, b && k.lazyAssign(b, k.nonComputedMember("l", a.name))); (k.state.expensiveChecks || Mb(a.name)) && k.addEnsureSafeObject(b); c(b); break; case t.MemberExpression: g = d && (d.context = this.nextId()) || this.nextId(); b = b || this.nextId(); k.recurse(a.object, g, void 0, function () {
                        k.if_(k.notNull(g), function () {
                            f && 1 !== f && k.addEnsureSafeAssignContext(g); if (a.computed) h = k.nextId(), k.recurse(a.property, h), k.getStringValue(h),
                                k.addEnsureSafeMemberName(h), f && 1 !== f && k.if_(k.not(k.computedMember(g, h)), k.lazyAssign(k.computedMember(g, h), "{}")), m = k.ensureSafeObject(k.computedMember(g, h)), k.assign(b, m), d && (d.computed = !0, d.name = h); else { Ua(a.property.name); f && 1 !== f && k.if_(k.not(k.nonComputedMember(g, a.property.name)), k.lazyAssign(k.nonComputedMember(g, a.property.name), "{}")); m = k.nonComputedMember(g, a.property.name); if (k.state.expensiveChecks || Mb(a.property.name)) m = k.ensureSafeObject(m); k.assign(b, m); d && (d.computed = !1, d.name = a.property.name) }
                        },
                            function () { k.assign(b, "undefined") }); c(b)
                    }, !!f); break; case t.CallExpression: b = b || this.nextId(); a.filter ? (h = k.filter(a.callee.name), l = [], q(a.arguments, function (a) { var b = k.nextId(); k.recurse(a, b); l.push(b) }), m = h + "(" + l.join(",") + ")", k.assign(b, m), c(b)) : (h = k.nextId(), g = {}, l = [], k.recurse(a.callee, h, g, function () {
                        k.if_(k.notNull(h), function () {
                            k.addEnsureSafeFunction(h); q(a.arguments, function (a) { k.recurse(a, k.nextId(), void 0, function (a) { l.push(k.ensureSafeObject(a)) }) }); g.name ? (k.state.expensiveChecks || k.addEnsureSafeObject(g.context),
                                m = k.member(g.context, g.name, g.computed) + "(" + l.join(",") + ")") : m = h + "(" + l.join(",") + ")"; m = k.ensureSafeObject(m); k.assign(b, m)
                        }, function () { k.assign(b, "undefined") }); c(b)
                    })); break; case t.AssignmentExpression: h = this.nextId(); g = {}; this.recurse(a.left, void 0, g, function () { k.if_(k.notNull(g.context), function () { k.recurse(a.right, h); k.addEnsureSafeObject(k.member(g.context, g.name, g.computed)); k.addEnsureSafeAssignContext(g.context); m = k.member(g.context, g.name, g.computed) + a.operator + h; k.assign(b, m); c(b || m) }) },
                        1); break; case t.ArrayExpression: l = []; q(a.elements, function (a) { k.recurse(a, k.nextId(), void 0, function (a) { l.push(a) }) }); m = "[" + l.join(",") + "]"; this.assign(b, m); c(m); break; case t.ObjectExpression: l = []; n = !1; q(a.properties, function (a) { a.computed && (n = !0) }); n ? (b = b || this.nextId(), this.assign(b, "{}"), q(a.properties, function (a) { a.computed ? (g = k.nextId(), k.recurse(a.key, g)) : g = a.key.type === t.Identifier ? a.key.name : "" + a.key.value; h = k.nextId(); k.recurse(a.value, h); k.assign(k.member(b, g, a.computed), h) })) : (q(a.properties,
                            function (b) { k.recurse(b.value, a.constant ? void 0 : k.nextId(), void 0, function (a) { l.push(k.escape(b.key.type === t.Identifier ? b.key.name : "" + b.key.value) + ":" + a) }) }), m = "{" + l.join(",") + "}", this.assign(b, m)); c(b || m); break; case t.ThisExpression: this.assign(b, "s"); c("s"); break; case t.LocalsExpression: this.assign(b, "l"); c("l"); break; case t.NGValueParameter: this.assign(b, "v"), c("v")
            }
        }, getHasOwnProperty: function (a, b) {
            var d = a + "." + b, c = this.current().own; c.hasOwnProperty(d) || (c[d] = this.nextId(!1, a + "&&(" + this.escape(b) +
                " in " + a + ")")); return c[d]
        }, assign: function (a, b) { if (a) return this.current().body.push(a, "=", b, ";"), a }, filter: function (a) { this.state.filters.hasOwnProperty(a) || (this.state.filters[a] = this.nextId(!0)); return this.state.filters[a] }, ifDefined: function (a, b) { return "ifDefined(" + a + "," + this.escape(b) + ")" }, plus: function (a, b) { return "plus(" + a + "," + b + ")" }, return_: function (a) { this.current().body.push("return ", a, ";") }, if_: function (a, b, d) {
            if (!0 === a) b(); else {
                var c = this.current().body; c.push("if(", a, "){"); b(); c.push("}");
                d && (c.push("else{"), d(), c.push("}"))
            }
        }, not: function (a) { return "!(" + a + ")" }, notNull: function (a) { return a + "!=null" }, nonComputedMember: function (a, b) { var d = /[^$_a-zA-Z0-9]/g; return /^[$_a-zA-Z][$_a-zA-Z0-9]*$/.test(b) ? a + "." + b : a + '["' + b.replace(d, this.stringEscapeFn) + '"]' }, computedMember: function (a, b) { return a + "[" + b + "]" }, member: function (a, b, d) { return d ? this.computedMember(a, b) : this.nonComputedMember(a, b) }, addEnsureSafeObject: function (a) { this.current().body.push(this.ensureSafeObject(a), ";") }, addEnsureSafeMemberName: function (a) {
            this.current().body.push(this.ensureSafeMemberName(a),
                ";")
        }, addEnsureSafeFunction: function (a) { this.current().body.push(this.ensureSafeFunction(a), ";") }, addEnsureSafeAssignContext: function (a) { this.current().body.push(this.ensureSafeAssignContext(a), ";") }, ensureSafeObject: function (a) { return "ensureSafeObject(" + a + ",text)" }, ensureSafeMemberName: function (a) { return "ensureSafeMemberName(" + a + ",text)" }, ensureSafeFunction: function (a) { return "ensureSafeFunction(" + a + ",text)" }, getStringValue: function (a) { this.assign(a, "getStringValue(" + a + ")") }, ensureSafeAssignContext: function (a) {
            return "ensureSafeAssignContext(" +
                a + ",text)"
        }, lazyRecurse: function (a, b, d, c, f, e) { var g = this; return function () { g.recurse(a, b, d, c, f, e) } }, lazyAssign: function (a, b) { var d = this; return function () { d.assign(a, b) } }, stringEscapeRegex: /[^ a-zA-Z0-9]/g, stringEscapeFn: function (a) { return "\\u" + ("0000" + a.charCodeAt(0).toString(16)).slice(-4) }, escape: function (a) {
            if (C(a)) return "'" + a.replace(this.stringEscapeRegex, this.stringEscapeFn) + "'"; if (ba(a)) return a.toString(); if (!0 === a) return "true"; if (!1 === a) return "false"; if (null === a) return "null"; if ("undefined" ===
                typeof a) return "undefined"; throw ea("esc");
        }, nextId: function (a, b) { var d = "v" + this.state.nextId++; a || this.current().vars.push(d + (b ? "=" + b : "")); return d }, current: function () { return this.state[this.state.computing] }
    }; Jd.prototype = {
        compile: function (a, b) {
            var d = this, c = this.astBuilder.ast(a); this.expression = a; this.expensiveChecks = b; X(c, d.$filter); var f, e; if (f = Gd(c)) e = this.recurse(f); f = Ed(c.body); var g; f && (g = [], q(f, function (a, b) { var c = d.recurse(a); a.input = c; g.push(c); a.watchId = b })); var h = []; q(c.body, function (a) { h.push(d.recurse(a.expression)) });
            f = 0 === c.body.length ? y : 1 === c.body.length ? h[0] : function (a, b) { var c; q(h, function (d) { c = d(a, b) }); return c }; e && (f.assign = function (a, b, c) { return e(a, c, b) }); g && (f.inputs = g); f.literal = Hd(c); f.constant = c.constant; return f
        }, recurse: function (a, b, d) {
            var c, f, e = this, g; if (a.input) return this.inputs(a.input, a.watchId); switch (a.type) {
                case t.Literal: return this.value(a.value, b); case t.UnaryExpression: return f = this.recurse(a.argument), this["unary" + a.operator](f, b); case t.BinaryExpression: return c = this.recurse(a.left),
                    f = this.recurse(a.right), this["binary" + a.operator](c, f, b); case t.LogicalExpression: return c = this.recurse(a.left), f = this.recurse(a.right), this["binary" + a.operator](c, f, b); case t.ConditionalExpression: return this["ternary?:"](this.recurse(a.test), this.recurse(a.alternate), this.recurse(a.consequent), b); case t.Identifier: return Ua(a.name, e.expression), e.identifier(a.name, e.expensiveChecks || Mb(a.name), b, d, e.expression); case t.MemberExpression: return c = this.recurse(a.object, !1, !!d), a.computed || (Ua(a.property.name,
                        e.expression), f = a.property.name), a.computed && (f = this.recurse(a.property)), a.computed ? this.computedMember(c, f, b, d, e.expression) : this.nonComputedMember(c, f, e.expensiveChecks, b, d, e.expression); case t.CallExpression: return g = [], q(a.arguments, function (a) { g.push(e.recurse(a)) }), a.filter && (f = this.$filter(a.callee.name)), a.filter || (f = this.recurse(a.callee, !0)), a.filter ? function (a, c, d, e) {
                            for (var n = [], r = 0; r < g.length; ++r)n.push(g[r](a, c, d, e)); a = f.apply(void 0, n, e); return b ? { context: void 0, name: void 0, value: a } :
                                a
                        } : function (a, c, d, m) { var n = f(a, c, d, m), r; if (null != n.value) { Ea(n.context, e.expression); vd(n.value, e.expression); r = []; for (var s = 0; s < g.length; ++s)r.push(Ea(g[s](a, c, d, m), e.expression)); r = Ea(n.value.apply(n.context, r), e.expression) } return b ? { value: r } : r }; case t.AssignmentExpression: return c = this.recurse(a.left, !0, 1), f = this.recurse(a.right), function (a, d, g, m) { var n = c(a, d, g, m); a = f(a, d, g, m); Ea(n.value, e.expression); Kb(n.context); n.context[n.name] = a; return b ? { value: a } : a }; case t.ArrayExpression: return g = [], q(a.elements,
                            function (a) { g.push(e.recurse(a)) }), function (a, c, d, e) { for (var f = [], r = 0; r < g.length; ++r)f.push(g[r](a, c, d, e)); return b ? { value: f } : f }; case t.ObjectExpression: return g = [], q(a.properties, function (a) { a.computed ? g.push({ key: e.recurse(a.key), computed: !0, value: e.recurse(a.value) }) : g.push({ key: a.key.type === t.Identifier ? a.key.name : "" + a.key.value, computed: !1, value: e.recurse(a.value) }) }), function (a, c, d, e) {
                                for (var f = {}, r = 0; r < g.length; ++r)g[r].computed ? f[g[r].key(a, c, d, e)] = g[r].value(a, c, d, e) : f[g[r].key] = g[r].value(a,
                                    c, d, e); return b ? { value: f } : f
                            }; case t.ThisExpression: return function (a) { return b ? { value: a } : a }; case t.LocalsExpression: return function (a, c) { return b ? { value: c } : c }; case t.NGValueParameter: return function (a, c, d) { return b ? { value: d } : d }
            }
        }, "unary+": function (a, b) { return function (d, c, f, e) { d = a(d, c, f, e); d = x(d) ? +d : 0; return b ? { value: d } : d } }, "unary-": function (a, b) { return function (d, c, f, e) { d = a(d, c, f, e); d = x(d) ? -d : 0; return b ? { value: d } : d } }, "unary!": function (a, b) {
            return function (d, c, f, e) {
                d = !a(d, c, f, e); return b ? { value: d } :
                    d
            }
        }, "binary+": function (a, b, d) { return function (c, f, e, g) { var h = a(c, f, e, g); c = b(c, f, e, g); h = Dd(h, c); return d ? { value: h } : h } }, "binary-": function (a, b, d) { return function (c, f, e, g) { var h = a(c, f, e, g); c = b(c, f, e, g); h = (x(h) ? h : 0) - (x(c) ? c : 0); return d ? { value: h } : h } }, "binary*": function (a, b, d) { return function (c, f, e, g) { c = a(c, f, e, g) * b(c, f, e, g); return d ? { value: c } : c } }, "binary/": function (a, b, d) { return function (c, f, e, g) { c = a(c, f, e, g) / b(c, f, e, g); return d ? { value: c } : c } }, "binary%": function (a, b, d) {
            return function (c, f, e, g) {
                c = a(c, f,
                    e, g) % b(c, f, e, g); return d ? { value: c } : c
            }
        }, "binary===": function (a, b, d) { return function (c, f, e, g) { c = a(c, f, e, g) === b(c, f, e, g); return d ? { value: c } : c } }, "binary!==": function (a, b, d) { return function (c, f, e, g) { c = a(c, f, e, g) !== b(c, f, e, g); return d ? { value: c } : c } }, "binary==": function (a, b, d) { return function (c, f, e, g) { c = a(c, f, e, g) == b(c, f, e, g); return d ? { value: c } : c } }, "binary!=": function (a, b, d) { return function (c, f, e, g) { c = a(c, f, e, g) != b(c, f, e, g); return d ? { value: c } : c } }, "binary<": function (a, b, d) {
            return function (c, f, e, g) {
                c = a(c, f,
                    e, g) < b(c, f, e, g); return d ? { value: c } : c
            }
        }, "binary>": function (a, b, d) { return function (c, f, e, g) { c = a(c, f, e, g) > b(c, f, e, g); return d ? { value: c } : c } }, "binary<=": function (a, b, d) { return function (c, f, e, g) { c = a(c, f, e, g) <= b(c, f, e, g); return d ? { value: c } : c } }, "binary>=": function (a, b, d) { return function (c, f, e, g) { c = a(c, f, e, g) >= b(c, f, e, g); return d ? { value: c } : c } }, "binary&&": function (a, b, d) { return function (c, f, e, g) { c = a(c, f, e, g) && b(c, f, e, g); return d ? { value: c } : c } }, "binary||": function (a, b, d) {
            return function (c, f, e, g) {
                c = a(c, f, e, g) ||
                b(c, f, e, g); return d ? { value: c } : c
            }
        }, "ternary?:": function (a, b, d, c) { return function (f, e, g, h) { f = a(f, e, g, h) ? b(f, e, g, h) : d(f, e, g, h); return c ? { value: f } : f } }, value: function (a, b) { return function () { return b ? { context: void 0, name: void 0, value: a } : a } }, identifier: function (a, b, d, c, f) { return function (e, g, h, k) { e = g && a in g ? g : e; c && 1 !== c && e && !e[a] && (e[a] = {}); g = e ? e[a] : void 0; b && Ea(g, f); return d ? { context: e, name: a, value: g } : g } }, computedMember: function (a, b, d, c, f) {
            return function (e, g, h, k) {
                var l = a(e, g, h, k), m, n; null != l && (m = b(e,
                    g, h, k), m += "", Ua(m, f), c && 1 !== c && (Kb(l), l && !l[m] && (l[m] = {})), n = l[m], Ea(n, f)); return d ? { context: l, name: m, value: n } : n
            }
        }, nonComputedMember: function (a, b, d, c, f, e) { return function (g, h, k, l) { g = a(g, h, k, l); f && 1 !== f && (Kb(g), g && !g[b] && (g[b] = {})); h = null != g ? g[b] : void 0; (d || Mb(b)) && Ea(h, e); return c ? { context: g, name: b, value: h } : h } }, inputs: function (a, b) { return function (d, c, f, e) { return e ? e[b] : a(d, c, f) } }
    }; var oc = function (a, b, d) {
        this.lexer = a; this.$filter = b; this.options = d; this.ast = new t(a, d); this.astCompiler = d.csp ? new Jd(this.ast,
            b) : new Id(this.ast, b)
    }; oc.prototype = { constructor: oc, parse: function (a) { return this.astCompiler.compile(a, this.options.expensiveChecks) } }; var Fa = G("$sce"), ra = { HTML: "html", CSS: "css", URL: "url", RESOURCE_URL: "resourceUrl", JS: "js" }, Mg = G("$compile"), aa = w.document.createElement("a"), Nd = ta(w.location.href); Od.$inject = ["$document"]; Tc.$inject = ["$provide"]; var Vd = 22, Ud = ".", qc = "0"; Pd.$inject = ["$locale"]; Rd.$inject = ["$locale"]; var Xg = {
        yyyy: V("FullYear", 4, 0, !1, !0), yy: V("FullYear", 2, 0, !0, !0), y: V("FullYear", 1, 0, !1,
            !0), MMMM: ob("Month"), MMM: ob("Month", !0), MM: V("Month", 2, 1), M: V("Month", 1, 1), LLLL: ob("Month", !1, !0), dd: V("Date", 2), d: V("Date", 1), HH: V("Hours", 2), H: V("Hours", 1), hh: V("Hours", 2, -12), h: V("Hours", 1, -12), mm: V("Minutes", 2), m: V("Minutes", 1), ss: V("Seconds", 2), s: V("Seconds", 1), sss: V("Milliseconds", 3), EEEE: ob("Day"), EEE: ob("Day", !0), a: function (a, b) { return 12 > a.getHours() ? b.AMPMS[0] : b.AMPMS[1] }, Z: function (a, b, d) { a = -1 * d; return a = (0 <= a ? "+" : "") + (Nb(Math[0 < a ? "floor" : "ceil"](a / 60), 2) + Nb(Math.abs(a % 60), 2)) }, ww: Xd(2),
        w: Xd(1), G: rc, GG: rc, GGG: rc, GGGG: function (a, b) { return 0 >= a.getFullYear() ? b.ERANAMES[0] : b.ERANAMES[1] }
    }, Wg = /((?:[^yMLdHhmsaZEwG']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|L+|d+|H+|h+|m+|s+|a|Z|G+|w+))(.*)/, Vg = /^-?\d+$/; Qd.$inject = ["$locale"]; var Qg = ga(Q), Rg = ga(xb); Sd.$inject = ["$parse"]; var Ie = ga({
        restrict: "E", compile: function (a, b) {
            if (!b.href && !b.xlinkHref) return function (a, b) {
                if ("a" === b[0].nodeName.toLowerCase()) {
                    var f = "[object SVGAnimatedString]" === ma.call(b.prop("href")) ? "xlink:href" : "href"; b.on("click", function (a) {
                        b.attr(f) ||
                        a.preventDefault()
                    })
                }
            }
        }
    }), yb = {}; q(Hb, function (a, b) { function d(a, d, f) { a.$watch(f[c], function (a) { f.$set(b, !!a) }) } if ("multiple" !== a) { var c = Da("ng-" + b), f = d; "checked" === a && (f = function (a, b, f) { f.ngModel !== f[c] && d(a, b, f) }); yb[c] = function () { return { restrict: "A", priority: 100, link: f } } } }); q(id, function (a, b) {
        yb[b] = function () {
            return {
                priority: 100, link: function (a, c, f) {
                    if ("ngPattern" === b && "/" === f.ngPattern.charAt(0) && (c = f.ngPattern.match($g))) { f.$set("ngPattern", new RegExp(c[1], c[2])); return } a.$watch(f[b], function (a) {
                        f.$set(b,
                            a)
                    })
                }
            }
        }
    }); q(["src", "srcset", "href"], function (a) { var b = Da("ng-" + a); yb[b] = function () { return { priority: 99, link: function (d, c, f) { var e = a, g = a; "href" === a && "[object SVGAnimatedString]" === ma.call(c.prop("href")) && (g = "xlinkHref", f.$attr[g] = "xlink:href", e = null); f.$observe(b, function (b) { b ? (f.$set(g, b), Ga && e && c.prop(e, f[g])) : "href" === a && f.$set(g, null) }) } } } }); var Ob = { $addControl: y, $$renameControl: function (a, b) { a.$name = b }, $removeControl: y, $setValidity: y, $setDirty: y, $setPristine: y, $setSubmitted: y }; Yd.$inject = ["$element",
        "$attrs", "$scope", "$animate", "$interpolate"]; var ie = function (a) {
            return ["$timeout", "$parse", function (b, d) {
                function c(a) { return "" === a ? d('this[""]').assign : d(a).assign || y } return {
                    name: "form", restrict: a ? "EAC" : "E", require: ["form", "^^?form"], controller: Yd, compile: function (d, e) {
                        d.addClass(Wa).addClass(sb); var g = e.name ? "name" : a && e.ngForm ? "ngForm" : !1; return {
                            pre: function (a, d, e, f) {
                                var n = f[0]; if (!("action" in e)) {
                                    var r = function (b) { a.$apply(function () { n.$commitViewValue(); n.$setSubmitted() }); b.preventDefault() };
                                    d[0].addEventListener("submit", r, !1); d.on("$destroy", function () { b(function () { d[0].removeEventListener("submit", r, !1) }, 0, !1) })
                                } (f[1] || n.$$parentForm).$addControl(n); var s = g ? c(n.$name) : y; g && (s(a, n), e.$observe(g, function (b) { n.$name !== b && (s(a, void 0), n.$$parentForm.$$renameControl(n, b), s = c(n.$name), s(a, n)) })); d.on("$destroy", function () { n.$$parentForm.$removeControl(n); s(a, void 0); R(n, Ob) })
                            }
                        }
                    }
                }
            }]
        }, Je = ie(), Ve = ie(!0), Yg = /^\d{4,}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d\.\d+(?:[+-][0-2]\d:[0-5]\d|Z)$/, hh = /^[a-z][a-z\d.+-]*:\/*(?:[^:@]+(?::[^@]+)?@)?(?:[^\s:/?#]+|\[[a-f\d:]+])(?::\d+)?(?:\/[^?#]*)?(?:\?[^#]*)?(?:#.*)?$/i,
            ih = /^(?=.{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])?)*$/, Zg = /^\s*(-|\+)?(\d+|(\d*(\.\d*)))([eE][+-]?\d+)?\s*$/, je = /^(\d{4,})-(\d{2})-(\d{2})$/, ke = /^(\d{4,})-(\d\d)-(\d\d)T(\d\d):(\d\d)(?::(\d\d)(\.\d{1,3})?)?$/, yc = /^(\d{4,})-W(\d\d)$/, le = /^(\d{4,})-(\d\d)$/, me = /^(\d\d):(\d\d)(?::(\d\d)(\.\d{1,3})?)?$/, $d = W(); q(["date", "datetime-local", "month", "time", "week"], function (a) {
                $d[a] =
                !0
            }); var ne = {
                text: function (a, b, d, c, f, e) { Xa(a, b, d, c, f, e); tc(c) }, date: pb("date", je, Qb(je, ["yyyy", "MM", "dd"]), "yyyy-MM-dd"), "datetime-local": pb("datetimelocal", ke, Qb(ke, "yyyy MM dd HH mm ss sss".split(" ")), "yyyy-MM-ddTHH:mm:ss.sss"), time: pb("time", me, Qb(me, ["HH", "mm", "ss", "sss"]), "HH:mm:ss.sss"), week: pb("week", yc, function (a, b) {
                    if (ja(a)) return a; if (C(a)) {
                        yc.lastIndex = 0; var d = yc.exec(a); if (d) {
                            var c = +d[1], f = +d[2], e = d = 0, g = 0, h = 0, k = Wd(c), f = 7 * (f - 1); b && (d = b.getHours(), e = b.getMinutes(), g = b.getSeconds(), h = b.getMilliseconds());
                            return new Date(c, 0, k.getDate() + f, d, e, g, h)
                        }
                    } return NaN
                }, "yyyy-Www"), month: pb("month", le, Qb(le, ["yyyy", "MM"]), "yyyy-MM"), number: function (a, b, d, c, f, e) { uc(a, b, d, c); Xa(a, b, d, c, f, e); ae(c); var g, h; if (x(d.min) || d.ngMin) c.$validators.min = function (a) { return c.$isEmpty(a) || z(g) || a >= g }, d.$observe("min", function (a) { g = rb(a); c.$validate() }); if (x(d.max) || d.ngMax) c.$validators.max = function (a) { return c.$isEmpty(a) || z(h) || a <= h }, d.$observe("max", function (a) { h = rb(a); c.$validate() }) }, url: function (a, b, d, c, f, e) {
                    Xa(a, b,
                        d, c, f, e); tc(c); c.$$parserName = "url"; c.$validators.url = function (a, b) { var d = a || b; return c.$isEmpty(d) || hh.test(d) }
                }, email: function (a, b, d, c, f, e) { Xa(a, b, d, c, f, e); tc(c); c.$$parserName = "email"; c.$validators.email = function (a, b) { var d = a || b; return c.$isEmpty(d) || ih.test(d) } }, radio: function (a, b, d, c) { z(d.name) && b.attr("name", ++tb); b.on("click", function (a) { b[0].checked && c.$setViewValue(d.value, a && a.type) }); c.$render = function () { b[0].checked = d.value == c.$viewValue }; d.$observe("value", c.$render) }, range: function (a,
                    b, d, c, f, e) {
                        function g(a, c) { b.attr(a, d[a]); d.$observe(a, c) } function h(a) { n = rb(a); ha(c.$modelValue) || (m ? (a = b.val(), n > a && (a = n, b.val(a)), c.$setViewValue(a)) : c.$validate()) } function k(a) { r = rb(a); ha(c.$modelValue) || (m ? (a = b.val(), r < a && (b.val(r), a = r < n ? n : r), c.$setViewValue(a)) : c.$validate()) } function l(a) { s = rb(a); ha(c.$modelValue) || (m && c.$viewValue !== b.val() ? c.$setViewValue(b.val()) : c.$validate()) } uc(a, b, d, c); ae(c); Xa(a, b, d, c, f, e); var m = c.$$hasNativeValidators && "range" === b[0].type, n = m ? 0 : void 0, r = m ? 100 : void 0,
                            s = m ? 1 : void 0, q = b[0].validity; a = x(d.min); f = x(d.max); e = x(d.step); var u = c.$render; c.$render = m && x(q.rangeUnderflow) && x(q.rangeOverflow) ? function () { u(); c.$setViewValue(b.val()) } : u; a && (c.$validators.min = m ? function () { return !0 } : function (a, b) { return c.$isEmpty(b) || z(n) || b >= n }, g("min", h)); f && (c.$validators.max = m ? function () { return !0 } : function (a, b) { return c.$isEmpty(b) || z(r) || b <= r }, g("max", k)); e && (c.$validators.step = m ? function () { return !q.stepMismatch } : function (a, b) {
                                var d; if (!(d = c.$isEmpty(b) || z(s))) {
                                    d = n || 0; var e =
                                        s, f = Number(b); if ((f | 0) !== f || (d | 0) !== d || (e | 0) !== e) { var g = Math.max(vc(f), vc(d), vc(e)), g = Math.pow(10, g), f = f * g; d *= g; e *= g } d = 0 === (f - d) % e
                                } return d
                            }, g("step", l))
                }, checkbox: function (a, b, d, c, f, e, g, h) {
                    var k = be(h, a, "ngTrueValue", d.ngTrueValue, !0), l = be(h, a, "ngFalseValue", d.ngFalseValue, !1); b.on("click", function (a) { c.$setViewValue(b[0].checked, a && a.type) }); c.$render = function () { b[0].checked = c.$viewValue }; c.$isEmpty = function (a) { return !1 === a }; c.$formatters.push(function (a) { return na(a, k) }); c.$parsers.push(function (a) {
                        return a ?
                            k : l
                    })
                }, hidden: y, button: y, submit: y, reset: y, file: y
            }, Nc = ["$browser", "$sniffer", "$filter", "$parse", function (a, b, d, c) { return { restrict: "E", require: ["?ngModel"], link: { pre: function (f, e, g, h) { if (h[0]) { var k = Q(g.type); "range" !== k || g.hasOwnProperty("ngInputRange") || (k = "text"); (ne[k] || ne.text)(f, e, g, h[0], b, a, d, c) } } } } }], jh = /^(true|false|\d+)$/, nf = function () {
                return {
                    restrict: "A", priority: 100, compile: function (a, b) {
                        return jh.test(b.ngValue) ? function (a, b, f) { f.$set("value", a.$eval(f.ngValue)) } : function (a, b, f) {
                            a.$watch(f.ngValue,
                                function (a) { f.$set("value", a) })
                        }
                    }
                }
            }, Ne = ["$compile", function (a) { return { restrict: "AC", compile: function (b) { a.$$addBindingClass(b); return function (b, c, f) { a.$$addBindingInfo(c, f.ngBind); c = c[0]; b.$watch(f.ngBind, function (a) { c.textContent = z(a) ? "" : a }) } } } }], Pe = ["$interpolate", "$compile", function (a, b) {
                return {
                    compile: function (d) {
                        b.$$addBindingClass(d); return function (c, d, e) {
                            c = a(d.attr(e.$attr.ngBindTemplate)); b.$$addBindingInfo(d, c.expressions); d = d[0]; e.$observe("ngBindTemplate", function (a) {
                                d.textContent = z(a) ?
                                    "" : a
                            })
                        }
                    }
                }
            }], Oe = ["$sce", "$parse", "$compile", function (a, b, d) { return { restrict: "A", compile: function (c, f) { var e = b(f.ngBindHtml), g = b(f.ngBindHtml, function (b) { return a.valueOf(b) }); d.$$addBindingClass(c); return function (b, c, f) { d.$$addBindingInfo(c, f.ngBindHtml); b.$watch(g, function () { var d = e(b); c.html(a.getTrustedHtml(d) || "") }) } } } }], mf = ga({ restrict: "A", require: "ngModel", link: function (a, b, d, c) { c.$viewChangeListeners.push(function () { a.$eval(d.ngChange) }) } }), Qe = wc("", !0), Se = wc("Odd", 0), Re = wc("Even", 1), Te = Va({
                compile: function (a,
                    b) { b.$set("ngCloak", void 0); a.removeClass("ng-cloak") }
            }), Ue = [function () { return { restrict: "A", scope: !0, controller: "@", priority: 500 } }], Sc = {}, kh = { blur: !0, focus: !0 }; q("click dblclick mousedown mouseup mouseover mouseout mousemove mouseenter mouseleave keydown keyup keypress submit focus blur copy cut paste".split(" "), function (a) {
                var b = Da("ng-" + a); Sc[b] = ["$parse", "$rootScope", function (d, c) {
                    return {
                        restrict: "A", compile: function (f, e) {
                            var g = d(e[b], null, !0); return function (b, d) {
                                d.on(a, function (d) {
                                    var e = function () {
                                        g(b,
                                            { $event: d })
                                    }; kh[a] && c.$$phase ? b.$evalAsync(e) : b.$apply(e)
                                })
                            }
                        }
                    }
                }]
            }); var Xe = ["$animate", "$compile", function (a, b) { return { multiElement: !0, transclude: "element", priority: 600, terminal: !0, restrict: "A", $$tlb: !0, link: function (d, c, f, e, g) { var h, k, l; d.$watch(f.ngIf, function (d) { d ? k || g(function (d, e) { k = e; d[d.length++] = b.$$createComment("end ngIf", f.ngIf); h = { clone: d }; a.enter(d, c.parent(), c) }) : (l && (l.remove(), l = null), k && (k.$destroy(), k = null), h && (l = wb(h.clone), a.leave(l).done(function (a) { !1 !== a && (l = null) }), h = null)) }) } } }],
                Ye = ["$templateRequest", "$anchorScroll", "$animate", function (a, b, d) {
                    return {
                        restrict: "ECA", priority: 400, terminal: !0, transclude: "element", controller: $.noop, compile: function (c, f) {
                            var e = f.ngInclude || f.src, g = f.onload || "", h = f.autoscroll; return function (c, f, m, n, r) {
                                var q = 0, t, u, p, w = function () { u && (u.remove(), u = null); t && (t.$destroy(), t = null); p && (d.leave(p).done(function (a) { !1 !== a && (u = null) }), u = p, p = null) }; c.$watch(e, function (e) {
                                    var m = function (a) { !1 === a || !x(h) || h && !c.$eval(h) || b() }, u = ++q; e ? (a(e, !0).then(function (a) {
                                        if (!c.$$destroyed &&
                                            u === q) { var b = c.$new(); n.template = a; a = r(b, function (a) { w(); d.enter(a, null, f).done(m) }); t = b; p = a; t.$emit("$includeContentLoaded", e); c.$eval(g) }
                                    }, function () { c.$$destroyed || u !== q || (w(), c.$emit("$includeContentError", e)) }), c.$emit("$includeContentRequested", e)) : (w(), n.template = null)
                                })
                            }
                        }
                    }
                }], pf = ["$compile", function (a) {
                    return {
                        restrict: "ECA", priority: -400, require: "ngInclude", link: function (b, d, c, f) {
                            ma.call(d[0]).match(/SVG/) ? (d.empty(), a(Vc(f.template, w.document).childNodes)(b, function (a) { d.append(a) }, { futureParentElement: d })) :
                            (d.html(f.template), a(d.contents())(b))
                        }
                    }
                }], Ze = Va({ priority: 450, compile: function () { return { pre: function (a, b, d) { a.$eval(d.ngInit) } } } }), lf = function () { return { restrict: "A", priority: 100, require: "ngModel", link: function (a, b, d, c) { var f = b.attr(d.$attr.ngList) || ", ", e = "false" !== d.ngTrim, g = e ? Y(f) : f; c.$parsers.push(function (a) { if (!z(a)) { var b = []; a && q(a.split(g), function (a) { a && b.push(e ? Y(a) : a) }); return b } }); c.$formatters.push(function (a) { if (I(a)) return a.join(f) }); c.$isEmpty = function (a) { return !a || !a.length } } } },
                sb = "ng-valid", ce = "ng-invalid", Wa = "ng-pristine", Pb = "ng-dirty", ee = "ng-pending", qb = G("ngModel"), lh = ["$scope", "$exceptionHandler", "$attrs", "$element", "$parse", "$animate", "$timeout", "$rootScope", "$q", "$interpolate", function (a, b, d, c, f, e, g, h, k, l) {
                    this.$modelValue = this.$viewValue = Number.NaN; this.$$rawModelValue = void 0; this.$validators = {}; this.$asyncValidators = {}; this.$parsers = []; this.$formatters = []; this.$viewChangeListeners = []; this.$untouched = !0; this.$touched = !1; this.$pristine = !0; this.$dirty = !1; this.$valid =
                        !0; this.$invalid = !1; this.$error = {}; this.$$success = {}; this.$pending = void 0; this.$name = l(d.name || "", !1)(a); this.$$parentForm = Ob; var m = f(d.ngModel), n = m.assign, r = m, s = n, t = null, u, p = this; this.$$setOptions = function (a) { if ((p.$options = a) && a.getterSetter) { var b = f(d.ngModel + "()"), e = f(d.ngModel + "($$$p)"); r = function (a) { var c = m(a); D(c) && (c = b(a)); return c }; s = function (a, b) { D(m(a)) ? e(a, { $$$p: b }) : n(a, b) } } else if (!m.assign) throw qb("nonassign", d.ngModel, ya(c)); }; this.$render = y; this.$isEmpty = function (a) {
                            return z(a) ||
                                "" === a || null === a || a !== a
                        }; this.$$updateEmptyClasses = function (a) { p.$isEmpty(a) ? (e.removeClass(c, "ng-not-empty"), e.addClass(c, "ng-empty")) : (e.removeClass(c, "ng-empty"), e.addClass(c, "ng-not-empty")) }; var w = 0; Zd({ ctrl: this, $element: c, set: function (a, b) { a[b] = !0 }, unset: function (a, b) { delete a[b] }, $animate: e }); this.$setPristine = function () { p.$dirty = !1; p.$pristine = !0; e.removeClass(c, Pb); e.addClass(c, Wa) }; this.$setDirty = function () { p.$dirty = !0; p.$pristine = !1; e.removeClass(c, Wa); e.addClass(c, Pb); p.$$parentForm.$setDirty() };
                    this.$setUntouched = function () { p.$touched = !1; p.$untouched = !0; e.setClass(c, "ng-untouched", "ng-touched") }; this.$setTouched = function () { p.$touched = !0; p.$untouched = !1; e.setClass(c, "ng-touched", "ng-untouched") }; this.$rollbackViewValue = function () { g.cancel(t); p.$viewValue = p.$$lastCommittedViewValue; p.$render() }; this.$validate = function () {
                        if (!ha(p.$modelValue)) {
                            var a = p.$$rawModelValue, b = p.$valid, c = p.$modelValue, d = p.$options && p.$options.allowInvalid; p.$$runValidators(a, p.$$lastCommittedViewValue, function (e) {
                                d ||
                                b === e || (p.$modelValue = e ? a : void 0, p.$modelValue !== c && p.$$writeModelToScope())
                            })
                        }
                    }; this.$$runValidators = function (a, b, c) {
                        function d() { var c = !0; q(p.$validators, function (d, e) { var g = d(a, b); c = c && g; f(e, g) }); return c ? !0 : (q(p.$asyncValidators, function (a, b) { f(b, null) }), !1) } function e() { var c = [], d = !0; q(p.$asyncValidators, function (e, g) { var h = e(a, b); if (!h || !D(h.then)) throw qb("nopromise", h); f(g, void 0); c.push(h.then(function () { f(g, !0) }, function () { d = !1; f(g, !1) })) }); c.length ? k.all(c).then(function () { g(d) }, y) : g(!0) }
                        function f(a, b) { h === w && p.$setValidity(a, b) } function g(a) { h === w && c(a) } w++; var h = w; (function () { var a = p.$$parserName || "parse"; if (z(u)) f(a, null); else return u || (q(p.$validators, function (a, b) { f(b, null) }), q(p.$asyncValidators, function (a, b) { f(b, null) })), f(a, u), u; return !0 })() ? d() ? e() : g(!1) : g(!1)
                    }; this.$commitViewValue = function () {
                        var a = p.$viewValue; g.cancel(t); if (p.$$lastCommittedViewValue !== a || "" === a && p.$$hasNativeValidators) p.$$updateEmptyClasses(a), p.$$lastCommittedViewValue = a, p.$pristine && this.$setDirty(),
                            this.$$parseAndValidate()
                    }; this.$$parseAndValidate = function () { var b = p.$$lastCommittedViewValue; if (u = z(b) ? void 0 : !0) for (var c = 0; c < p.$parsers.length; c++)if (b = p.$parsers[c](b), z(b)) { u = !1; break } ha(p.$modelValue) && (p.$modelValue = r(a)); var d = p.$modelValue, e = p.$options && p.$options.allowInvalid; p.$$rawModelValue = b; e && (p.$modelValue = b, p.$modelValue !== d && p.$$writeModelToScope()); p.$$runValidators(b, p.$$lastCommittedViewValue, function (a) { e || (p.$modelValue = a ? b : void 0, p.$modelValue !== d && p.$$writeModelToScope()) }) };
                    this.$$writeModelToScope = function () { s(a, p.$modelValue); q(p.$viewChangeListeners, function (a) { try { a() } catch (c) { b(c) } }) }; this.$setViewValue = function (a, b) { p.$viewValue = a; p.$options && !p.$options.updateOnDefault || p.$$debounceViewValueCommit(b) }; this.$$debounceViewValueCommit = function (b) { var c = 0, d = p.$options; d && x(d.debounce) && (d = d.debounce, ba(d) ? c = d : ba(d[b]) ? c = d[b] : ba(d["default"]) && (c = d["default"])); g.cancel(t); c ? t = g(function () { p.$commitViewValue() }, c) : h.$$phase ? p.$commitViewValue() : a.$apply(function () { p.$commitViewValue() }) };
                    a.$watch(function () { var b = r(a); if (b !== p.$modelValue && (p.$modelValue === p.$modelValue || b === b)) { p.$modelValue = p.$$rawModelValue = b; u = void 0; for (var c = p.$formatters, d = c.length, e = b; d--;)e = c[d](e); p.$viewValue !== e && (p.$$updateEmptyClasses(e), p.$viewValue = p.$$lastCommittedViewValue = e, p.$render(), p.$$runValidators(p.$modelValue, p.$viewValue, y)) } return b })
                }], kf = ["$rootScope", function (a) {
                    return {
                        restrict: "A", require: ["ngModel", "^?form", "^?ngModelOptions"], controller: lh, priority: 1, compile: function (b) {
                            b.addClass(Wa).addClass("ng-untouched").addClass(sb);
                            return { pre: function (a, b, f, e) { var g = e[0]; b = e[1] || g.$$parentForm; g.$$setOptions(e[2] && e[2].$options); b.$addControl(g); f.$observe("name", function (a) { g.$name !== a && g.$$parentForm.$$renameControl(g, a) }); a.$on("$destroy", function () { g.$$parentForm.$removeControl(g) }) }, post: function (b, c, f, e) { var g = e[0]; if (g.$options && g.$options.updateOn) c.on(g.$options.updateOn, function (a) { g.$$debounceViewValueCommit(a && a.type) }); c.on("blur", function () { g.$touched || (a.$$phase ? b.$evalAsync(g.$setTouched) : b.$apply(g.$setTouched)) }) } }
                        }
                    }
                }],
                mh = /(\s+|^)default(\s+|$)/, of = function () { return { restrict: "A", controller: ["$scope", "$attrs", function (a, b) { var d = this; this.$options = sa(a.$eval(b.ngModelOptions)); x(this.$options.updateOn) ? (this.$options.updateOnDefault = !1, this.$options.updateOn = Y(this.$options.updateOn.replace(mh, function () { d.$options.updateOnDefault = !0; return " " }))) : this.$options.updateOnDefault = !0 }] } }, $e = Va({ terminal: !0, priority: 1E3 }), nh = G("ngOptions"), oh = /^\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]+?))?$/,
                hf = ["$compile", "$document", "$parse", function (a, b, d) {
                    function c(a, b, c) {
                        function e(a, b, c, d, f) { this.selectValue = a; this.viewValue = b; this.label = c; this.group = d; this.disabled = f } function f(a) { var b; if (!q && la(a)) b = a; else { b = []; for (var c in a) a.hasOwnProperty(c) && "$" !== c.charAt(0) && b.push(c) } return b } var n = a.match(oh); if (!n) throw nh("iexp", a, ya(b)); var r = n[5] || n[7], q = n[6]; a = / as /.test(n[0]) && n[1]; var t = n[9]; b = d(n[2] ? n[1] : r); var u = a && d(a) || b, p = t && d(t), x = t ? function (a, b) { return p(c, b) } : function (a) { return Aa(a) },
                            A = function (a, b) { return x(a, C(a, b)) }, v = d(n[2] || n[1]), w = d(n[3] || ""), z = d(n[4] || ""), D = d(n[8]), y = {}, C = q ? function (a, b) { y[q] = b; y[r] = a; return y } : function (a) { y[r] = a; return y }; return {
                                trackBy: t, getTrackByValue: A, getWatchables: d(D, function (a) { var b = []; a = a || []; for (var d = f(a), e = d.length, g = 0; g < e; g++) { var h = a === d ? g : d[g], l = a[h], h = C(l, h), l = x(l, h); b.push(l); if (n[2] || n[1]) l = v(c, h), b.push(l); n[4] && (h = z(c, h), b.push(h)) } return b }), getOptions: function () {
                                    for (var a = [], b = {}, d = D(c) || [], g = f(d), h = g.length, n = 0; n < h; n++) {
                                        var p = d ===
                                            g ? n : g[n], r = C(d[p], p), q = u(c, r), p = x(q, r), s = v(c, r), y = w(c, r), r = z(c, r), q = new e(p, q, s, y, r); a.push(q); b[p] = q
                                    } return { items: a, selectValueMap: b, getOptionFromViewValue: function (a) { return b[A(a)] }, getViewValueFromOption: function (a) { return t ? sa(a.viewValue) : a.viewValue } }
                                }
                            }
                    } var f = w.document.createElement("option"), e = w.document.createElement("optgroup"); return {
                        restrict: "A", terminal: !0, require: ["select", "ngModel"], link: {
                            pre: function (a, b, c, d) { d[0].registerOption = y }, post: function (d, h, k, l) {
                                function m(a, b) {
                                    a.element =
                                    b; b.disabled = a.disabled; a.label !== b.label && (b.label = a.label, b.textContent = a.label); b.value = a.selectValue
                                } function n() {
                                    var a = y && r.readValue(); if (y) for (var b = y.items.length - 1; 0 <= b; b--) { var c = y.items[b]; x(c.group) ? Gb(c.element.parentNode) : Gb(c.element) } y = D.getOptions(); var d = {}; A && h.prepend(u); y.items.forEach(function (a) {
                                        var b; if (x(a.group)) { b = d[a.group]; b || (b = e.cloneNode(!1), C.appendChild(b), b.label = null === a.group ? "null" : a.group, d[a.group] = b); var c = f.cloneNode(!1) } else b = C, c = f.cloneNode(!1); b.appendChild(c);
                                        m(a, c)
                                    }); h[0].appendChild(C); s.$render(); s.$isEmpty(a) || (b = r.readValue(), (D.trackBy || t ? na(a, b) : a === b) || (s.$setViewValue(b), s.$render()))
                                } var r = l[0], s = l[1], t = k.multiple, u; l = 0; for (var p = h.children(), w = p.length; l < w; l++)if ("" === p[l].value) { u = p.eq(l); break } var A = !!u, v = !1, z = F(f.cloneNode(!1)); z.val("?"); var y, D = c(k.ngOptions, h, d), C = b[0].createDocumentFragment(), E = function () { A ? v && u.removeAttr("selected") : u.remove() }; t ? (s.$isEmpty = function (a) { return !a || 0 === a.length }, r.writeValue = function (a) {
                                    y.items.forEach(function (a) {
                                        a.element.selected =
                                        !1
                                    }); a && a.forEach(function (a) { if (a = y.getOptionFromViewValue(a)) a.element.selected = !0 })
                                }, r.readValue = function () { var a = h.val() || [], b = []; q(a, function (a) { (a = y.selectValueMap[a]) && !a.disabled && b.push(y.getViewValueFromOption(a)) }); return b }, D.trackBy && d.$watchCollection(function () { if (I(s.$viewValue)) return s.$viewValue.map(function (a) { return D.getTrackByValue(a) }) }, function () { s.$render() })) : (r.writeValue = function (a) {
                                    var b = y.selectValueMap[h.val()], c = y.getOptionFromViewValue(a); b && b.element.removeAttribute("selected");
                                    c ? (h[0].value !== c.selectValue && (z.remove(), E(), h[0].value = c.selectValue, c.element.selected = !0), c.element.setAttribute("selected", "selected")) : null === a || A ? (z.remove(), A || h.prepend(u), h.val(""), v && (u.prop("selected", !0), u.attr("selected", !0))) : (E(), h.prepend(z), h.val("?"), z.prop("selected", !0), z.attr("selected", !0))
                                }, r.readValue = function () { var a = y.selectValueMap[h.val()]; return a && !a.disabled ? (E(), z.remove(), y.getViewValueFromOption(a)) : null }, D.trackBy && d.$watch(function () { return D.getTrackByValue(s.$viewValue) },
                                    function () { s.$render() })); A ? (u.remove(), a(u)(d), 8 === u[0].nodeType ? (v = !1, r.registerOption = function (a, b) { "" === b.val() && (v = !0, u = b, u.removeClass("ng-scope"), s.$render(), b.on("$destroy", function () { u = void 0; v = !1 })) }) : (u.removeClass("ng-scope"), v = !0)) : u = F(f.cloneNode(!1)); h.empty(); n(); d.$watchCollection(D.getWatchables, n)
                            }
                        }
                    }
                }], af = ["$locale", "$interpolate", "$log", function (a, b, d) {
                    var c = /{}/g, f = /^when(Minus)?(.+)$/; return {
                        link: function (e, g, h) {
                            function k(a) { g.text(a || "") } var l = h.count, m = h.$attr.when && g.attr(h.$attr.when),
                                n = h.offset || 0, r = e.$eval(m) || {}, s = {}, t = b.startSymbol(), u = b.endSymbol(), p = t + l + "-" + n + u, x = $.noop, A; q(h, function (a, b) { var c = f.exec(b); c && (c = (c[1] ? "-" : "") + Q(c[2]), r[c] = g.attr(h.$attr[b])) }); q(r, function (a, d) { s[d] = b(a.replace(c, p)) }); e.$watch(l, function (b) { var c = parseFloat(b), f = ha(c); f || c in r || (c = a.pluralCat(c - n)); c === A || f && ha(A) || (x(), f = s[c], z(f) ? (null != b && d.debug("ngPluralize: no rule defined for '" + c + "' in " + m), x = y, k()) : x = e.$watch(f, k), A = c) })
                        }
                    }
                }], bf = ["$parse", "$animate", "$compile", function (a, b, d) {
                    var c =
                        G("ngRepeat"), f = function (a, b, c, d, f, m, n) { a[c] = d; f && (a[f] = m); a.$index = b; a.$first = 0 === b; a.$last = b === n - 1; a.$middle = !(a.$first || a.$last); a.$odd = !(a.$even = 0 === (b & 1)) }; return {
                            restrict: "A", multiElement: !0, transclude: "element", priority: 1E3, terminal: !0, $$tlb: !0, compile: function (e, g) {
                                var h = g.ngRepeat, k = d.$$createComment("end ngRepeat", h), l = h.match(/^\s*([\s\S]+?)\s+in\s+([\s\S]+?)(?:\s+as\s+([\s\S]+?))?(?:\s+track\s+by\s+([\s\S]+?))?\s*$/); if (!l) throw c("iexp", h); var m = l[1], n = l[2], r = l[3], s = l[4], l = m.match(/^(?:(\s*[$\w]+)|\(\s*([$\w]+)\s*,\s*([$\w]+)\s*\))$/);
                                if (!l) throw c("iidexp", m); var t = l[3] || l[1], u = l[2]; if (r && (!/^[$a-zA-Z_][$a-zA-Z0-9_]*$/.test(r) || /^(null|undefined|this|\$index|\$first|\$middle|\$last|\$even|\$odd|\$parent|\$root|\$id)$/.test(r))) throw c("badident", r); var p, x, A, v, w = { $id: Aa }; s ? p = a(s) : (A = function (a, b) { return Aa(b) }, v = function (a) { return a }); return function (a, d, e, g, l) {
                                    p && (x = function (b, c, d) { u && (w[u] = b); w[t] = c; w.$index = d; return p(a, w) }); var m = W(); a.$watchCollection(n, function (e) {
                                        var g, n, p = d[0], s, w = W(), z, y, D, C, F, E, G; r && (a[r] = e); if (la(e)) F =
                                            e, n = x || A; else for (G in n = x || v, F = [], e) ua.call(e, G) && "$" !== G.charAt(0) && F.push(G); z = F.length; G = Array(z); for (g = 0; g < z; g++)if (y = e === F ? g : F[g], D = e[y], C = n(y, D, g), m[C]) E = m[C], delete m[C], w[C] = E, G[g] = E; else { if (w[C]) throw q(G, function (a) { a && a.scope && (m[a.id] = a) }), c("dupes", h, C, D); G[g] = { id: C, scope: void 0, clone: void 0 }; w[C] = !0 } for (s in m) { E = m[s]; C = wb(E.clone); b.leave(C); if (C[0].parentNode) for (g = 0, n = C.length; g < n; g++)C[g].$$NG_REMOVED = !0; E.scope.$destroy() } for (g = 0; g < z; g++)if (y = e === F ? g : F[g], D = e[y], E = G[g], E.scope) {
                                                s =
                                                p; do s = s.nextSibling; while (s && s.$$NG_REMOVED); E.clone[0] !== s && b.move(wb(E.clone), null, p); p = E.clone[E.clone.length - 1]; f(E.scope, g, t, D, u, y, z)
                                            } else l(function (a, c) { E.scope = c; var d = k.cloneNode(!1); a[a.length++] = d; b.enter(a, null, p); p = d; E.clone = a; w[E.id] = E; f(E.scope, g, t, D, u, y, z) }); m = w
                                    })
                                }
                            }
                        }
                }], cf = ["$animate", function (a) { return { restrict: "A", multiElement: !0, link: function (b, d, c) { b.$watch(c.ngShow, function (b) { a[b ? "removeClass" : "addClass"](d, "ng-hide", { tempClasses: "ng-hide-animate" }) }) } } }], We = ["$animate", function (a) {
                    return {
                        restrict: "A",
                        multiElement: !0, link: function (b, d, c) { b.$watch(c.ngHide, function (b) { a[b ? "addClass" : "removeClass"](d, "ng-hide", { tempClasses: "ng-hide-animate" }) }) }
                    }
                }], df = Va(function (a, b, d) { a.$watch(d.ngStyle, function (a, d) { d && a !== d && q(d, function (a, c) { b.css(c, "") }); a && b.css(a) }, !0) }), ef = ["$animate", "$compile", function (a, b) {
                    return {
                        require: "ngSwitch", controller: ["$scope", function () { this.cases = {} }], link: function (d, c, f, e) {
                            var g = [], h = [], k = [], l = [], m = function (a, b) { return function (c) { !1 !== c && a.splice(b, 1) } }; d.$watch(f.ngSwitch ||
                                f.on, function (c) { for (var d, f; k.length;)a.cancel(k.pop()); d = 0; for (f = l.length; d < f; ++d) { var t = wb(h[d].clone); l[d].$destroy(); (k[d] = a.leave(t)).done(m(k, d)) } h.length = 0; l.length = 0; (g = e.cases["!" + c] || e.cases["?"]) && q(g, function (c) { c.transclude(function (d, e) { l.push(e); var f = c.element; d[d.length++] = b.$$createComment("end ngSwitchWhen"); h.push({ clone: d }); a.enter(d, f.parent(), f) }) }) })
                        }
                    }
                }], ff = Va({
                    transclude: "element", priority: 1200, require: "^ngSwitch", multiElement: !0, link: function (a, b, d, c, f) {
                        a = d.ngSwitchWhen.split(d.ngSwitchWhenSeparator).sort().filter(function (a,
                            b, c) { return c[b - 1] !== a }); q(a, function (a) { c.cases["!" + a] = c.cases["!" + a] || []; c.cases["!" + a].push({ transclude: f, element: b }) })
                    }
                }), gf = Va({ transclude: "element", priority: 1200, require: "^ngSwitch", multiElement: !0, link: function (a, b, d, c, f) { c.cases["?"] = c.cases["?"] || []; c.cases["?"].push({ transclude: f, element: b }) } }), ph = G("ngTransclude"), jf = ["$compile", function (a) {
                    return {
                        restrict: "EAC", terminal: !0, compile: function (b) {
                            var d = a(b.contents()); b.empty(); return function (a, b, e, g, h) {
                                function k() { d(a, function (a) { b.append(a) }) }
                                if (!h) throw ph("orphan", ya(b)); e.ngTransclude === e.$attr.ngTransclude && (e.ngTransclude = ""); e = e.ngTransclude || e.ngTranscludeSlot; h(function (a, c) { a.length ? b.append(a) : (k(), c.$destroy()) }, null, e); e && !h.isSlotFilled(e) && k()
                            }
                        }
                    }
                }], Ke = ["$templateCache", function (a) { return { restrict: "E", terminal: !0, compile: function (b, d) { "text/ng-template" === d.type && a.put(d.id, b[0].text) } } }], qh = { $setViewValue: y, $render: y }, rh = ["$element", "$scope", function (a, b) {
                    var d = this, c = new Sa; d.ngModelCtrl = qh; d.unknownOption = F(w.document.createElement("option"));
                    d.renderUnknownOption = function (b) { b = "? " + Aa(b) + " ?"; d.unknownOption.val(b); a.prepend(d.unknownOption); a.val(b) }; b.$on("$destroy", function () { d.renderUnknownOption = y }); d.removeUnknownOption = function () { d.unknownOption.parent() && d.unknownOption.remove() }; d.readValue = function () { d.removeUnknownOption(); return a.val() }; d.writeValue = function (b) { d.hasOption(b) ? (d.removeUnknownOption(), a.val(b), "" === b && d.emptyOption.prop("selected", !0)) : null == b && d.emptyOption ? (d.removeUnknownOption(), a.val("")) : d.renderUnknownOption(b) };
                    d.addOption = function (a, b) { if (8 !== b[0].nodeType) { Ra(a, '"option value"'); "" === a && (d.emptyOption = b); var g = c.get(a) || 0; c.put(a, g + 1); d.ngModelCtrl.$render(); b[0].hasAttribute("selected") && (b[0].selected = !0) } }; d.removeOption = function (a) { var b = c.get(a); b && (1 === b ? (c.remove(a), "" === a && (d.emptyOption = void 0)) : c.put(a, b - 1)) }; d.hasOption = function (a) { return !!c.get(a) }; d.registerOption = function (a, b, c, h, k) {
                        if (h) { var l; c.$observe("value", function (a) { x(l) && d.removeOption(l); l = a; d.addOption(a, b) }) } else k ? a.$watch(k,
                            function (a, f) { c.$set("value", a); f !== a && d.removeOption(f); d.addOption(a, b) }) : d.addOption(c.value, b); b.on("$destroy", function () { d.removeOption(c.value); d.ngModelCtrl.$render() })
                    }
                }], Le = function () {
                    return {
                        restrict: "E", require: ["select", "?ngModel"], controller: rh, priority: 1, link: {
                            pre: function (a, b, d, c) {
                                var f = c[1]; if (f) {
                                    var e = c[0]; e.ngModelCtrl = f; b.on("change", function () { a.$apply(function () { f.$setViewValue(e.readValue()) }) }); if (d.multiple) {
                                        e.readValue = function () {
                                            var a = []; q(b.find("option"), function (b) {
                                                b.selected &&
                                                a.push(b.value)
                                            }); return a
                                        }; e.writeValue = function (a) { var c = new Sa(a); q(b.find("option"), function (a) { a.selected = x(c.get(a.value)) }) }; var g, h = NaN; a.$watch(function () { h !== f.$viewValue || na(g, f.$viewValue) || (g = ka(f.$viewValue), f.$render()); h = f.$viewValue }); f.$isEmpty = function (a) { return !a || 0 === a.length }
                                    }
                                }
                            }, post: function (a, b, d, c) { var f = c[1]; if (f) { var e = c[0]; f.$render = function () { e.writeValue(f.$viewValue) } } }
                        }
                    }
                }, Me = ["$interpolate", function (a) {
                    return {
                        restrict: "E", priority: 100, compile: function (b, d) {
                            var c, f;
                            x(d.ngValue) ? c = !0 : x(d.value) ? c = a(d.value, !0) : (f = a(b.text(), !0)) || d.$set("value", b.text()); return function (a, b, d) { var k = b.parent(); (k = k.data("$selectController") || k.parent().data("$selectController")) && k.registerOption(a, b, d, c, f) }
                        }
                    }
                }], Pc = function () { return { restrict: "A", require: "?ngModel", link: function (a, b, d, c) { c && (d.required = !0, c.$validators.required = function (a, b) { return !d.required || !c.$isEmpty(b) }, d.$observe("required", function () { c.$validate() })) } } }, Oc = function () {
                    return {
                        restrict: "A", require: "?ngModel",
                        link: function (a, b, d, c) { if (c) { var f, e = d.ngPattern || d.pattern; d.$observe("pattern", function (a) { C(a) && 0 < a.length && (a = new RegExp("^" + a + "$")); if (a && !a.test) throw G("ngPattern")("noregexp", e, a, ya(b)); f = a || void 0; c.$validate() }); c.$validators.pattern = function (a, b) { return c.$isEmpty(b) || z(f) || f.test(b) } } }
                    }
                }, Rc = function () {
                    return {
                        restrict: "A", require: "?ngModel", link: function (a, b, d, c) {
                            if (c) {
                                var f = -1; d.$observe("maxlength", function (a) { a = Z(a); f = ha(a) ? -1 : a; c.$validate() }); c.$validators.maxlength = function (a, b) {
                                    return 0 >
                                        f || c.$isEmpty(b) || b.length <= f
                                }
                            }
                        }
                    }
                }, Qc = function () { return { restrict: "A", require: "?ngModel", link: function (a, b, d, c) { if (c) { var f = 0; d.$observe("minlength", function (a) { f = Z(a) || 0; c.$validate() }); c.$validators.minlength = function (a, b) { return c.$isEmpty(b) || b.length >= f } } } } }; w.angular.bootstrap ? w.console && console.log("WARNING: Tried to load angular more than once.") : (Be(), Fe($), $.module("ngLocale", [], ["$provide", function (a) {
                    function b(a) { a += ""; var b = a.indexOf("."); return -1 == b ? 0 : a.length - b - 1 } a.value("$locale",
                        {
                            DATETIME_FORMATS: {
                                AMPMS: ["AM", "PM"], DAY: "Sunday Monday Tuesday Wednesday Thursday Friday Saturday".split(" "), ERANAMES: ["Before Christ", "Anno Domini"], ERAS: ["BC", "AD"], FIRSTDAYOFWEEK: 6, MONTH: "January February March April May June July August September October November December".split(" "), SHORTDAY: "Sun Mon Tue Wed Thu Fri Sat".split(" "), SHORTMONTH: "Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec".split(" "), STANDALONEMONTH: "January February March April May June July August September October November December".split(" "),
                                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 (a,
                                c) { var f = a | 0, e = c; void 0 === e && (e = Math.min(b(a), 3)); Math.pow(10, e); return 1 == f && 0 == e ? "one" : "other" }
                        })
                }]), F(w.document).ready(function () { we(w.document, Ic) }))
})(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;}'));
//# sourceMappingURL=angular.min.js.map

/**
 * @license AngularJS v1.5.0
 * (c) 2010-2016 Google, Inc. http://angularjs.org
 * License: MIT
 */
(function (window, angular, undefined) {
    'use strict';

    /**
     * @ngdoc module
     * @name ngAria
     * @description
     *
     * The `ngAria` module provides support for common
     * [<abbr title="Accessible Rich Internet Applications">ARIA</abbr>](http://www.w3.org/TR/wai-aria/)
     * attributes that convey state or semantic information about the application for users
     * of assistive technologies, such as screen readers.
     *
     * <div doc-module-components="ngAria"></div>
     *
     * ## Usage
     *
     * For ngAria to do its magic, simply include the module `ngAria` as a dependency. The following
     * directives are supported:
     * `ngModel`, `ngChecked`, `ngRequired`, `ngValue`, `ngDisabled`, `ngShow`, `ngHide`, `ngClick`,
     * `ngDblClick`, and `ngMessages`.
     *
     * Below is a more detailed breakdown of the attributes handled by ngAria:
     *
     * | Directive                                   | Supported Attributes                                                                   |
     * |---------------------------------------------|----------------------------------------------------------------------------------------|
     * | {@link ng.directive:ngModel ngModel}        | aria-checked, aria-valuemin, aria-valuemax, aria-valuenow, aria-invalid, aria-required, input roles |
     * | {@link ng.directive:ngDisabled ngDisabled}  | aria-disabled                                                                          |
     * | {@link ng.directive:ngRequired ngRequired}  | aria-required                                                                          |
     * | {@link ng.directive:ngChecked ngChecked}    | aria-checked                                                                           |
     * | {@link ng.directive:ngValue ngValue}        | aria-checked                                                                           |
     * | {@link ng.directive:ngShow ngShow}          | aria-hidden                                                                            |
     * | {@link ng.directive:ngHide ngHide}          | aria-hidden                                                                            |
     * | {@link ng.directive:ngDblclick ngDblclick}  | tabindex                                                                               |
     * | {@link module:ngMessages ngMessages}        | aria-live                                                                              |
     * | {@link ng.directive:ngClick ngClick}        | tabindex, keypress event, button role                                                  |
     *
     * Find out more information about each directive by reading the
     * {@link guide/accessibility ngAria Developer Guide}.
     *
     * ##Example
     * Using ngDisabled with ngAria:
     * ```html
     * <md-checkbox ng-disabled="disabled">
     * ```
     * Becomes:
     * ```html
     * <md-checkbox ng-disabled="disabled" aria-disabled="true">
     * ```
     *
     * ##Disabling Attributes
     * It's possible to disable individual attributes added by ngAria with the
     * {@link ngAria.$ariaProvider#config config} method. For more details, see the
     * {@link guide/accessibility Developer Guide}.
     */
    /* global -ngAriaModule */
    var ngAriaModule = angular.module('ngAria', ['ng']).
                            provider('$aria', $AriaProvider);

    /**
    * Internal Utilities
    */
    var nodeBlackList = ['BUTTON', 'A', 'INPUT', 'TEXTAREA', 'SELECT', 'DETAILS', 'SUMMARY'];

    var isNodeOneOf = function (elem, nodeTypeArray) {
        if (nodeTypeArray.indexOf(elem[0].nodeName) !== -1) {
            return true;
        }
    };
    /**
     * @ngdoc provider
     * @name $ariaProvider
     *
     * @description
     *
     * Used for configuring the ARIA attributes injected and managed by ngAria.
     *
     * ```js
     * angular.module('myApp', ['ngAria'], function config($ariaProvider) {
     *   $ariaProvider.config({
     *     ariaValue: true,
     *     tabindex: false
     *   });
     * });
     *```
     *
     * ## Dependencies
     * Requires the {@link ngAria} module to be installed.
     *
     */
    function $AriaProvider() {
        var config = {
            ariaHidden: true,
            ariaChecked: true,
            ariaDisabled: true,
            ariaRequired: true,
            ariaInvalid: true,
            ariaValue: true,
            tabindex: true,
            bindKeypress: true,
            bindRoleForClick: true
        };

        /**
         * @ngdoc method
         * @name $ariaProvider#config
         *
         * @param {object} config object to enable/disable specific ARIA attributes
         *
         *  - **ariaHidden** – `{boolean}` – Enables/disables aria-hidden tags
         *  - **ariaChecked** – `{boolean}` – Enables/disables aria-checked tags
         *  - **ariaDisabled** – `{boolean}` – Enables/disables aria-disabled tags
         *  - **ariaRequired** – `{boolean}` – Enables/disables aria-required tags
         *  - **ariaInvalid** – `{boolean}` – Enables/disables aria-invalid tags
         *  - **ariaValue** – `{boolean}` – Enables/disables aria-valuemin, aria-valuemax and aria-valuenow tags
         *  - **tabindex** – `{boolean}` – Enables/disables tabindex tags
         *  - **bindKeypress** – `{boolean}` – Enables/disables keypress event binding on `div` and
         *    `li` elements with ng-click
         *  - **bindRoleForClick** – `{boolean}` – Adds role=button to non-interactive elements like `div`
         *    using ng-click, making them more accessible to users of assistive technologies
         *
         * @description
         * Enables/disables various ARIA attributes
         */
        this.config = function (newConfig) {
            config = angular.extend(config, newConfig);
        };

        function watchExpr(attrName, ariaAttr, nodeBlackList, negate) {
            return function (scope, elem, attr) {
                var ariaCamelName = attr.$normalize(ariaAttr);
                if (config[ariaCamelName] && !isNodeOneOf(elem, nodeBlackList) && !attr[ariaCamelName]) {
                    scope.$watch(attr[attrName], function (boolVal) {
                        // ensure boolean value
                        boolVal = negate ? !boolVal : !!boolVal;
                        elem.attr(ariaAttr, boolVal);
                    });
                }
            };
        }
        /**
         * @ngdoc service
         * @name $aria
         *
         * @description
         * @priority 200
         *
         * The $aria service contains helper methods for applying common
         * [ARIA](http://www.w3.org/TR/wai-aria/) attributes to HTML directives.
         *
         * ngAria injects common accessibility attributes that tell assistive technologies when HTML
         * elements are enabled, selected, hidden, and more. To see how this is performed with ngAria,
         * let's review a code snippet from ngAria itself:
         *
         *```js
         * ngAriaModule.directive('ngDisabled', ['$aria', function($aria) {
         *   return $aria.$$watchExpr('ngDisabled', 'aria-disabled', nodeBlackList, false);
         * }])
         *```
         * Shown above, the ngAria module creates a directive with the same signature as the
         * traditional `ng-disabled` directive. But this ngAria version is dedicated to
         * solely managing accessibility attributes on custom elements. The internal `$aria` service is
         * used to watch the boolean attribute `ngDisabled`. If it has not been explicitly set by the
         * developer, `aria-disabled` is injected as an attribute with its value synchronized to the
         * value in `ngDisabled`.
         *
         * Because ngAria hooks into the `ng-disabled` directive, developers do not have to do
         * anything to enable this feature. The `aria-disabled` attribute is automatically managed
         * simply as a silent side-effect of using `ng-disabled` with the ngAria module.
         *
         * The full list of directives that interface with ngAria:
         * * **ngModel**
         * * **ngChecked**
         * * **ngRequired**
         * * **ngDisabled**
         * * **ngValue**
         * * **ngShow**
         * * **ngHide**
         * * **ngClick**
         * * **ngDblclick**
         * * **ngMessages**
         *
         * Read the {@link guide/accessibility ngAria Developer Guide} for a thorough explanation of each
         * directive.
         *
         *
         * ## Dependencies
         * Requires the {@link ngAria} module to be installed.
         */
        this.$get = function () {
            return {
                config: function (key) {
                    return config[key];
                },
                $$watchExpr: watchExpr
            };
        };
    }


    ngAriaModule.directive('ngShow', ['$aria', function ($aria) {
        return $aria.$$watchExpr('ngShow', 'aria-hidden', [], true);
    }])
    .directive('ngHide', ['$aria', function ($aria) {
        return $aria.$$watchExpr('ngHide', 'aria-hidden', [], false);
    }])
    .directive('ngValue', ['$aria', function ($aria) {
        return $aria.$$watchExpr('ngValue', 'aria-checked', nodeBlackList, false);
    }])
    .directive('ngChecked', ['$aria', function ($aria) {
        return $aria.$$watchExpr('ngChecked', 'aria-checked', nodeBlackList, false);
    }])
    .directive('ngRequired', ['$aria', function ($aria) {
        return $aria.$$watchExpr('ngRequired', 'aria-required', nodeBlackList, false);
    }])
    .directive('ngModel', ['$aria', function ($aria) {

        function shouldAttachAttr(attr, normalizedAttr, elem, allowBlacklistEls) {
            return $aria.config(normalizedAttr) && !elem.attr(attr) && (allowBlacklistEls || !isNodeOneOf(elem, nodeBlackList));
        }

        function shouldAttachRole(role, elem) {
            // if element does not have role attribute
            // AND element type is equal to role (if custom element has a type equaling shape) <-- remove?
            // AND element is not INPUT
            return !elem.attr('role') && (elem.attr('type') === role) && (elem[0].nodeName !== 'INPUT');
        }

        function getShape(attr, elem) {
            var type = attr.type,
                role = attr.role;

            return ((type || role) === 'checkbox' || role === 'menuitemcheckbox') ? 'checkbox' :
                   ((type || role) === 'radio' || role === 'menuitemradio') ? 'radio' :
                   (type === 'range' || role === 'progressbar' || role === 'slider') ? 'range' : '';
        }

        return {
            restrict: 'A',
            require: 'ngModel',
            priority: 200, //Make sure watches are fired after any other directives that affect the ngModel value
            compile: function (elem, attr) {
                var shape = getShape(attr, elem);

                return {
                    pre: function (scope, elem, attr, ngModel) {
                        if (shape === 'checkbox') {
                            //Use the input[checkbox] $isEmpty implementation for elements with checkbox roles
                            ngModel.$isEmpty = function (value) {
                                return value === false;
                            };
                        }
                    },
                    post: function (scope, elem, attr, ngModel) {
                        var needsTabIndex = shouldAttachAttr('tabindex', 'tabindex', elem, false);

                        function ngAriaWatchModelValue() {
                            return ngModel.$modelValue;
                        }

                        function getRadioReaction(newVal) {
                            var boolVal = (attr.value == ngModel.$viewValue);
                            elem.attr('aria-checked', boolVal);
                        }

                        function getCheckboxReaction() {
                            elem.attr('aria-checked', !ngModel.$isEmpty(ngModel.$viewValue));
                        }

                        switch (shape) {
                            case 'radio':
                            case 'checkbox':
                                if (shouldAttachRole(shape, elem)) {
                                    elem.attr('role', shape);
                                }
                                if (shouldAttachAttr('aria-checked', 'ariaChecked', elem, false)) {
                                    scope.$watch(ngAriaWatchModelValue, shape === 'radio' ?
                                        getRadioReaction : getCheckboxReaction);
                                }
                                if (needsTabIndex) {
                                    elem.attr('tabindex', 0);
                                }
                                break;
                            case 'range':
                                if (shouldAttachRole(shape, elem)) {
                                    elem.attr('role', 'slider');
                                }
                                if ($aria.config('ariaValue')) {
                                    var needsAriaValuemin = !elem.attr('aria-valuemin') &&
                                        (attr.hasOwnProperty('min') || attr.hasOwnProperty('ngMin'));
                                    var needsAriaValuemax = !elem.attr('aria-valuemax') &&
                                        (attr.hasOwnProperty('max') || attr.hasOwnProperty('ngMax'));
                                    var needsAriaValuenow = !elem.attr('aria-valuenow');

                                    if (needsAriaValuemin) {
                                        attr.$observe('min', function ngAriaValueMinReaction(newVal) {
                                            elem.attr('aria-valuemin', newVal);
                                        });
                                    }
                                    if (needsAriaValuemax) {
                                        attr.$observe('max', function ngAriaValueMinReaction(newVal) {
                                            elem.attr('aria-valuemax', newVal);
                                        });
                                    }
                                    if (needsAriaValuenow) {
                                        scope.$watch(ngAriaWatchModelValue, function ngAriaValueNowReaction(newVal) {
                                            elem.attr('aria-valuenow', newVal);
                                        });
                                    }
                                }
                                if (needsTabIndex) {
                                    elem.attr('tabindex', 0);
                                }
                                break;
                        }

                        if (!attr.hasOwnProperty('ngRequired') && ngModel.$validators.required
                          && shouldAttachAttr('aria-required', 'ariaRequired', elem, false)) {
                            // ngModel.$error.required is undefined on custom controls
                            attr.$observe('required', function () {
                                elem.attr('aria-required', !!attr['required']);
                            });
                        }

                        if (shouldAttachAttr('aria-invalid', 'ariaInvalid', elem, true)) {
                            scope.$watch(function ngAriaInvalidWatch() {
                                return ngModel.$invalid;
                            }, function ngAriaInvalidReaction(newVal) {
                                elem.attr('aria-invalid', !!newVal);
                            });
                        }
                    }
                };
            }
        };
    }])
    .directive('ngDisabled', ['$aria', function ($aria) {
        return $aria.$$watchExpr('ngDisabled', 'aria-disabled', nodeBlackList, false);
    }])
    .directive('ngMessages', function () {
        return {
            restrict: 'A',
            require: '?ngMessages',
            link: function (scope, elem, attr, ngMessages) {
                if (!elem.attr('aria-live')) {
                    elem.attr('aria-live', 'assertive');
                }
            }
        };
    })
    .directive('ngClick', ['$aria', '$parse', function ($aria, $parse) {
        return {
            restrict: 'A',
            compile: function (elem, attr) {
                var fn = $parse(attr.ngClick, /* interceptorFn */ null, /* expensiveChecks */ true);
                return function (scope, elem, attr) {

                    if (!isNodeOneOf(elem, nodeBlackList)) {

                        if ($aria.config('bindRoleForClick') && !elem.attr('role')) {
                            elem.attr('role', 'button');
                        }

                        if ($aria.config('tabindex') && !elem.attr('tabindex')) {
                            elem.attr('tabindex', 0);
                        }

                        if ($aria.config('bindKeypress') && !attr.ngKeypress) {
                            elem.on('keypress', function (event) {
                                var keyCode = event.which || event.keyCode;
                                if (keyCode === 32 || keyCode === 13) {
                                    scope.$apply(callback);
                                }

                                function callback() {
                                    fn(scope, { $event: event });
                                }
                            });
                        }
                    }
                };
            }
        };
    }])
    .directive('ngDblclick', ['$aria', function ($aria) {
        return function (scope, elem, attr) {
            if ($aria.config('tabindex') && !elem.attr('tabindex') && !isNodeOneOf(elem, nodeBlackList)) {
                elem.attr('tabindex', 0);
            }
        };
    }]);


})(window, window.angular);
/**
 * State-based routing for AngularJS
 * @version v0.2.18
 * @link http://angular-ui.github.com/
 * @license MIT License, http://www.opensource.org/licenses/MIT
 */

/* commonjs package manager support (eg componentjs) */
if (typeof module !== "undefined" && typeof exports !== "undefined" && module.exports === exports){
  module.exports = 'ui.router';
}

(function (window, angular, undefined) {
/*jshint globalstrict:true*/
/*global angular:false*/
'use strict';

var isDefined = angular.isDefined,
    isFunction = angular.isFunction,
    isString = angular.isString,
    isObject = angular.isObject,
    isArray = angular.isArray,
    forEach = angular.forEach,
    extend = angular.extend,
    copy = angular.copy,
    toJson = angular.toJson;

function inherit(parent, extra) {
  return extend(new (extend(function() {}, { prototype: parent }))(), extra);
}

function merge(dst) {
  forEach(arguments, function(obj) {
    if (obj !== dst) {
      forEach(obj, function(value, key) {
        if (!dst.hasOwnProperty(key)) dst[key] = value;
      });
    }
  });
  return dst;
}

/**
 * Finds the common ancestor path between two states.
 *
 * @param {Object} first The first state.
 * @param {Object} second The second state.
 * @return {Array} Returns an array of state names in descending order, not including the root.
 */
function ancestors(first, second) {
  var path = [];

  for (var n in first.path) {
    if (first.path[n] !== second.path[n]) break;
    path.push(first.path[n]);
  }
  return path;
}

/**
 * IE8-safe wrapper for `Object.keys()`.
 *
 * @param {Object} object A JavaScript object.
 * @return {Array} Returns the keys of the object as an array.
 */
function objectKeys(object) {
  if (Object.keys) {
    return Object.keys(object);
  }
  var result = [];

  forEach(object, function(val, key) {
    result.push(key);
  });
  return result;
}

/**
 * IE8-safe wrapper for `Array.prototype.indexOf()`.
 *
 * @param {Array} array A JavaScript array.
 * @param {*} value A value to search the array for.
 * @return {Number} Returns the array index value of `value`, or `-1` if not present.
 */
function indexOf(array, value) {
  if (Array.prototype.indexOf) {
    return array.indexOf(value, Number(arguments[2]) || 0);
  }
  var len = array.length >>> 0, from = Number(arguments[2]) || 0;
  from = (from < 0) ? Math.ceil(from) : Math.floor(from);

  if (from < 0) from += len;

  for (; from < len; from++) {
    if (from in array && array[from] === value) return from;
  }
  return -1;
}

/**
 * Merges a set of parameters with all parameters inherited between the common parents of the
 * current state and a given destination state.
 *
 * @param {Object} currentParams The value of the current state parameters ($stateParams).
 * @param {Object} newParams The set of parameters which will be composited with inherited params.
 * @param {Object} $current Internal definition of object representing the current state.
 * @param {Object} $to Internal definition of object representing state to transition to.
 */
function inheritParams(currentParams, newParams, $current, $to) {
  var parents = ancestors($current, $to), parentParams, inherited = {}, inheritList = [];

  for (var i in parents) {
    if (!parents[i] || !parents[i].params) continue;
    parentParams = objectKeys(parents[i].params);
    if (!parentParams.length) continue;

    for (var j in parentParams) {
      if (indexOf(inheritList, parentParams[j]) >= 0) continue;
      inheritList.push(parentParams[j]);
      inherited[parentParams[j]] = currentParams[parentParams[j]];
    }
  }
  return extend({}, inherited, newParams);
}

/**
 * Performs a non-strict comparison of the subset of two objects, defined by a list of keys.
 *
 * @param {Object} a The first object.
 * @param {Object} b The second object.
 * @param {Array} keys The list of keys within each object to compare. If the list is empty or not specified,
 *                     it defaults to the list of keys in `a`.
 * @return {Boolean} Returns `true` if the keys match, otherwise `false`.
 */
function equalForKeys(a, b, keys) {
  if (!keys) {
    keys = [];
    for (var n in a) keys.push(n); // Used instead of Object.keys() for IE8 compatibility
  }

  for (var i=0; i<keys.length; i++) {
    var k = keys[i];
    if (a[k] != b[k]) return false; // Not '===', values aren't necessarily normalized
  }
  return true;
}

/**
 * Returns the subset of an object, based on a list of keys.
 *
 * @param {Array} keys
 * @param {Object} values
 * @return {Boolean} Returns a subset of `values`.
 */
function filterByKeys(keys, values) {
  var filtered = {};

  forEach(keys, function (name) {
    filtered[name] = values[name];
  });
  return filtered;
}

// like _.indexBy
// when you know that your index values will be unique, or you want last-one-in to win
function indexBy(array, propName) {
  var result = {};
  forEach(array, function(item) {
    result[item[propName]] = item;
  });
  return result;
}

// extracted from underscore.js
// Return a copy of the object only containing the whitelisted properties.
function pick(obj) {
  var copy = {};
  var keys = Array.prototype.concat.apply(Array.prototype, Array.prototype.slice.call(arguments, 1));
  forEach(keys, function(key) {
    if (key in obj) copy[key] = obj[key];
  });
  return copy;
}

// extracted from underscore.js
// Return a copy of the object omitting the blacklisted properties.
function omit(obj) {
  var copy = {};
  var keys = Array.prototype.concat.apply(Array.prototype, Array.prototype.slice.call(arguments, 1));
  for (var key in obj) {
    if (indexOf(keys, key) == -1) copy[key] = obj[key];
  }
  return copy;
}

function pluck(collection, key) {
  var result = isArray(collection) ? [] : {};

  forEach(collection, function(val, i) {
    result[i] = isFunction(key) ? key(val) : val[key];
  });
  return result;
}

function filter(collection, callback) {
  var array = isArray(collection);
  var result = array ? [] : {};
  forEach(collection, function(val, i) {
    if (callback(val, i)) {
      result[array ? result.length : i] = val;
    }
  });
  return result;
}

function map(collection, callback) {
  var result = isArray(collection) ? [] : {};

  forEach(collection, function(val, i) {
    result[i] = callback(val, i);
  });
  return result;
}

/**
 * @ngdoc overview
 * @name ui.router.util
 *
 * @description
 * # ui.router.util sub-module
 *
 * This module is a dependency of other sub-modules. Do not include this module as a dependency
 * in your angular app (use {@link ui.router} module instead).
 *
 */
angular.module('ui.router.util', ['ng']);

/**
 * @ngdoc overview
 * @name ui.router.router
 * 
 * @requires ui.router.util
 *
 * @description
 * # ui.router.router sub-module
 *
 * This module is a dependency of other sub-modules. Do not include this module as a dependency
 * in your angular app (use {@link ui.router} module instead).
 */
angular.module('ui.router.router', ['ui.router.util']);

/**
 * @ngdoc overview
 * @name ui.router.state
 * 
 * @requires ui.router.router
 * @requires ui.router.util
 *
 * @description
 * # ui.router.state sub-module
 *
 * This module is a dependency of the main ui.router module. Do not include this module as a dependency
 * in your angular app (use {@link ui.router} module instead).
 * 
 */
angular.module('ui.router.state', ['ui.router.router', 'ui.router.util']);

/**
 * @ngdoc overview
 * @name ui.router
 *
 * @requires ui.router.state
 *
 * @description
 * # ui.router
 * 
 * ## The main module for ui.router 
 * There are several sub-modules included with the ui.router module, however only this module is needed
 * as a dependency within your angular app. The other modules are for organization purposes. 
 *
 * The modules are:
 * * ui.router - the main "umbrella" module
 * * ui.router.router - 
 * 
 * *You'll need to include **only** this module as the dependency within your angular app.*
 * 
 * <pre>
 * <!doctype html>
 * <html ng-app="myApp">
 * <head>
 *   <script src="js/angular.min.js"></script>
 *   <!-- Include the ui-router script -->
 *   <script src="js/angular-ui-router.min.js"></script>
 *   <script>
 *     // ...and add 'ui.router' as a dependency
 *     var myApp = angular.module('myApp', ['ui.router']);
 *   </script>
 * </head>
 * <body>
 * </body>
 * </html>
 * </pre>
 */
angular.module('ui.router', ['ui.router.state']);

angular.module('ui.router.compat', ['ui.router']);

/**
 * @ngdoc object
 * @name ui.router.util.$resolve
 *
 * @requires $q
 * @requires $injector
 *
 * @description
 * Manages resolution of (acyclic) graphs of promises.
 */
$Resolve.$inject = ['$q', '$injector'];
function $Resolve(  $q,    $injector) {
  
  var VISIT_IN_PROGRESS = 1,
      VISIT_DONE = 2,
      NOTHING = {},
      NO_DEPENDENCIES = [],
      NO_LOCALS = NOTHING,
      NO_PARENT = extend($q.when(NOTHING), { $$promises: NOTHING, $$values: NOTHING });
  

  /**
   * @ngdoc function
   * @name ui.router.util.$resolve#study
   * @methodOf ui.router.util.$resolve
   *
   * @description
   * Studies a set of invocables that are likely to be used multiple times.
   * <pre>
   * $resolve.study(invocables)(locals, parent, self)
   * </pre>
   * is equivalent to
   * <pre>
   * $resolve.resolve(invocables, locals, parent, self)
   * </pre>
   * but the former is more efficient (in fact `resolve` just calls `study` 
   * internally).
   *
   * @param {object} invocables Invocable objects
   * @return {function} a function to pass in locals, parent and self
   */
  this.study = function (invocables) {
    if (!isObject(invocables)) throw new Error("'invocables' must be an object");
    var invocableKeys = objectKeys(invocables || {});
    
    // Perform a topological sort of invocables to build an ordered plan
    var plan = [], cycle = [], visited = {};
    function visit(value, key) {
      if (visited[key] === VISIT_DONE) return;
      
      cycle.push(key);
      if (visited[key] === VISIT_IN_PROGRESS) {
        cycle.splice(0, indexOf(cycle, key));
        throw new Error("Cyclic dependency: " + cycle.join(" -> "));
      }
      visited[key] = VISIT_IN_PROGRESS;
      
      if (isString(value)) {
        plan.push(key, [ function() { return $injector.get(value); }], NO_DEPENDENCIES);
      } else {
        var params = $injector.annotate(value);
        forEach(params, function (param) {
          if (param !== key && invocables.hasOwnProperty(param)) visit(invocables[param], param);
        });
        plan.push(key, value, params);
      }
      
      cycle.pop();
      visited[key] = VISIT_DONE;
    }
    forEach(invocables, visit);
    invocables = cycle = visited = null; // plan is all that's required
    
    function isResolve(value) {
      return isObject(value) && value.then && value.$$promises;
    }
    
    return function (locals, parent, self) {
      if (isResolve(locals) && self === undefined) {
        self = parent; parent = locals; locals = null;
      }
      if (!locals) locals = NO_LOCALS;
      else if (!isObject(locals)) {
        throw new Error("'locals' must be an object");
      }       
      if (!parent) parent = NO_PARENT;
      else if (!isResolve(parent)) {
        throw new Error("'parent' must be a promise returned by $resolve.resolve()");
      }
      
      // To complete the overall resolution, we have to wait for the parent
      // promise and for the promise for each invokable in our plan.
      var resolution = $q.defer(),
          result = resolution.promise,
          promises = result.$$promises = {},
          values = extend({}, locals),
          wait = 1 + plan.length/3,
          merged = false;
          
      function done() {
        // Merge parent values we haven't got yet and publish our own $$values
        if (!--wait) {
          if (!merged) merge(values, parent.$$values); 
          result.$$values = values;
          result.$$promises = result.$$promises || true; // keep for isResolve()
          delete result.$$inheritedValues;
          resolution.resolve(values);
        }
      }
      
      function fail(reason) {
        result.$$failure = reason;
        resolution.reject(reason);
      }

      // Short-circuit if parent has already failed
      if (isDefined(parent.$$failure)) {
        fail(parent.$$failure);
        return result;
      }
      
      if (parent.$$inheritedValues) {
        merge(values, omit(parent.$$inheritedValues, invocableKeys));
      }

      // Merge parent values if the parent has already resolved, or merge
      // parent promises and wait if the parent resolve is still in progress.
      extend(promises, parent.$$promises);
      if (parent.$$values) {
        merged = merge(values, omit(parent.$$values, invocableKeys));
        result.$$inheritedValues = omit(parent.$$values, invocableKeys);
        done();
      } else {
        if (parent.$$inheritedValues) {
          result.$$inheritedValues = omit(parent.$$inheritedValues, invocableKeys);
        }        
        parent.then(done, fail);
      }
      
      // Process each invocable in the plan, but ignore any where a local of the same name exists.
      for (var i=0, ii=plan.length; i<ii; i+=3) {
        if (locals.hasOwnProperty(plan[i])) done();
        else invoke(plan[i], plan[i+1], plan[i+2]);
      }
      
      function invoke(key, invocable, params) {
        // Create a deferred for this invocation. Failures will propagate to the resolution as well.
        var invocation = $q.defer(), waitParams = 0;
        function onfailure(reason) {
          invocation.reject(reason);
          fail(reason);
        }
        // Wait for any parameter that we have a promise for (either from parent or from this
        // resolve; in that case study() will have made sure it's ordered before us in the plan).
        forEach(params, function (dep) {
          if (promises.hasOwnProperty(dep) && !locals.hasOwnProperty(dep)) {
            waitParams++;
            promises[dep].then(function (result) {
              values[dep] = result;
              if (!(--waitParams)) proceed();
            }, onfailure);
          }
        });
        if (!waitParams) proceed();
        function proceed() {
          if (isDefined(result.$$failure)) return;
          try {
            invocation.resolve($injector.invoke(invocable, self, values));
            invocation.promise.then(function (result) {
              values[key] = result;
              done();
            }, onfailure);
          } catch (e) {
            onfailure(e);
          }
        }
        // Publish promise synchronously; invocations further down in the plan may depend on it.
        promises[key] = invocation.promise;
      }
      
      return result;
    };
  };
  
  /**
   * @ngdoc function
   * @name ui.router.util.$resolve#resolve
   * @methodOf ui.router.util.$resolve
   *
   * @description
   * Resolves a set of invocables. An invocable is a function to be invoked via 
   * `$injector.invoke()`, and can have an arbitrary number of dependencies. 
   * An invocable can either return a value directly,
   * or a `$q` promise. If a promise is returned it will be resolved and the 
   * resulting value will be used instead. Dependencies of invocables are resolved 
   * (in this order of precedence)
   *
   * - from the specified `locals`
   * - from another invocable that is part of this `$resolve` call
   * - from an invocable that is inherited from a `parent` call to `$resolve` 
   *   (or recursively
   * - from any ancestor `$resolve` of that parent).
   *
   * The return value of `$resolve` is a promise for an object that contains 
   * (in this order of precedence)
   *
   * - any `locals` (if specified)
   * - the resolved return values of all injectables
   * - any values inherited from a `parent` call to `$resolve` (if specified)
   *
   * The promise will resolve after the `parent` promise (if any) and all promises 
   * returned by injectables have been resolved. If any invocable 
   * (or `$injector.invoke`) throws an exception, or if a promise returned by an 
   * invocable is rejected, the `$resolve` promise is immediately rejected with the 
   * same error. A rejection of a `parent` promise (if specified) will likewise be 
   * propagated immediately. Once the `$resolve` promise has been rejected, no 
   * further invocables will be called.
   * 
   * Cyclic dependencies between invocables are not permitted and will cause `$resolve`
   * to throw an error. As a special case, an injectable can depend on a parameter 
   * with the same name as the injectable, which will be fulfilled from the `parent` 
   * injectable of the same name. This allows inherited values to be decorated. 
   * Note that in this case any other injectable in the same `$resolve` with the same
   * dependency would see the decorated value, not the inherited value.
   *
   * Note that missing dependencies -- unlike cyclic dependencies -- will cause an 
   * (asynchronous) rejection of the `$resolve` promise rather than a (synchronous) 
   * exception.
   *
   * Invocables are invoked eagerly as soon as all dependencies are available. 
   * This is true even for dependencies inherited from a `parent` call to `$resolve`.
   *
   * As a special case, an invocable can be a string, in which case it is taken to 
   * be a service name to be passed to `$injector.get()`. This is supported primarily 
   * for backwards-compatibility with the `resolve` property of `$routeProvider` 
   * routes.
   *
   * @param {object} invocables functions to invoke or 
   * `$injector` services to fetch.
   * @param {object} locals  values to make available to the injectables
   * @param {object} parent  a promise returned by another call to `$resolve`.
   * @param {object} self  the `this` for the invoked methods
   * @return {object} Promise for an object that contains the resolved return value
   * of all invocables, as well as any inherited and local values.
   */
  this.resolve = function (invocables, locals, parent, self) {
    return this.study(invocables)(locals, parent, self);
  };
}

angular.module('ui.router.util').service('$resolve', $Resolve);


/**
 * @ngdoc object
 * @name ui.router.util.$templateFactory
 *
 * @requires $http
 * @requires $templateCache
 * @requires $injector
 *
 * @description
 * Service. Manages loading of templates.
 */
$TemplateFactory.$inject = ['$http', '$templateCache', '$injector'];
function $TemplateFactory(  $http,   $templateCache,   $injector) {

  /**
   * @ngdoc function
   * @name ui.router.util.$templateFactory#fromConfig
   * @methodOf ui.router.util.$templateFactory
   *
   * @description
   * Creates a template from a configuration object. 
   *
   * @param {object} config Configuration object for which to load a template. 
   * The following properties are search in the specified order, and the first one 
   * that is defined is used to create the template:
   *
   * @param {string|object} config.template html string template or function to 
   * load via {@link ui.router.util.$templateFactory#fromString fromString}.
   * @param {string|object} config.templateUrl url to load or a function returning 
   * the url to load via {@link ui.router.util.$templateFactory#fromUrl fromUrl}.
   * @param {Function} config.templateProvider function to invoke via 
   * {@link ui.router.util.$templateFactory#fromProvider fromProvider}.
   * @param {object} params  Parameters to pass to the template function.
   * @param {object} locals Locals to pass to `invoke` if the template is loaded 
   * via a `templateProvider`. Defaults to `{ params: params }`.
   *
   * @return {string|object}  The template html as a string, or a promise for 
   * that string,or `null` if no template is configured.
   */
  this.fromConfig = function (config, params, locals) {
    return (
      isDefined(config.template) ? this.fromString(config.template, params) :
      isDefined(config.templateUrl) ? this.fromUrl(config.templateUrl, params) :
      isDefined(config.templateProvider) ? this.fromProvider(config.templateProvider, params, locals) :
      null
    );
  };

  /**
   * @ngdoc function
   * @name ui.router.util.$templateFactory#fromString
   * @methodOf ui.router.util.$templateFactory
   *
   * @description
   * Creates a template from a string or a function returning a string.
   *
   * @param {string|object} template html template as a string or function that 
   * returns an html template as a string.
   * @param {object} params Parameters to pass to the template function.
   *
   * @return {string|object} The template html as a string, or a promise for that 
   * string.
   */
  this.fromString = function (template, params) {
    return isFunction(template) ? template(params) : template;
  };

  /**
   * @ngdoc function
   * @name ui.router.util.$templateFactory#fromUrl
   * @methodOf ui.router.util.$templateFactory
   * 
   * @description
   * Loads a template from the a URL via `$http` and `$templateCache`.
   *
   * @param {string|Function} url url of the template to load, or a function 
   * that returns a url.
   * @param {Object} params Parameters to pass to the url function.
   * @return {string|Promise.<string>} The template html as a string, or a promise 
   * for that string.
   */
  this.fromUrl = function (url, params) {
    if (isFunction(url)) url = url(params);
    if (url == null) return null;
    else return $http
        .get(url, { cache: $templateCache, headers: { Accept: 'text/html' }})
        .then(function(response) { return response.data; });
  };

  /**
   * @ngdoc function
   * @name ui.router.util.$templateFactory#fromProvider
   * @methodOf ui.router.util.$templateFactory
   *
   * @description
   * Creates a template by invoking an injectable provider function.
   *
   * @param {Function} provider Function to invoke via `$injector.invoke`
   * @param {Object} params Parameters for the template.
   * @param {Object} locals Locals to pass to `invoke`. Defaults to 
   * `{ params: params }`.
   * @return {string|Promise.<string>} The template html as a string, or a promise 
   * for that string.
   */
  this.fromProvider = function (provider, params, locals) {
    return $injector.invoke(provider, null, locals || { params: params });
  };
}

angular.module('ui.router.util').service('$templateFactory', $TemplateFactory);

var $$UMFP; // reference to $UrlMatcherFactoryProvider

/**
 * @ngdoc object
 * @name ui.router.util.type:UrlMatcher
 *
 * @description
 * Matches URLs against patterns and extracts named parameters from the path or the search
 * part of the URL. A URL pattern consists of a path pattern, optionally followed by '?' and a list
 * of search parameters. Multiple search parameter names are separated by '&'. Search parameters
 * do not influence whether or not a URL is matched, but their values are passed through into
 * the matched parameters returned by {@link ui.router.util.type:UrlMatcher#methods_exec exec}.
 *
 * Path parameter placeholders can be specified using simple colon/catch-all syntax or curly brace
 * syntax, which optionally allows a regular expression for the parameter to be specified:
 *
 * * `':'` name - colon placeholder
 * * `'*'` name - catch-all placeholder
 * * `'{' name '}'` - curly placeholder
 * * `'{' name ':' regexp|type '}'` - curly placeholder with regexp or type name. Should the
 *   regexp itself contain curly braces, they must be in matched pairs or escaped with a backslash.
 *
 * Parameter names may contain only word characters (latin letters, digits, and underscore) and
 * must be unique within the pattern (across both path and search parameters). For colon
 * placeholders or curly placeholders without an explicit regexp, a path parameter matches any
 * number of characters other than '/'. For catch-all placeholders the path parameter matches
 * any number of characters.
 *
 * Examples:
 *
 * * `'/hello/'` - Matches only if the path is exactly '/hello/'. There is no special treatment for
 *   trailing slashes, and patterns have to match the entire path, not just a prefix.
 * * `'/user/:id'` - Matches '/user/bob' or '/user/1234!!!' or even '/user/' but not '/user' or
 *   '/user/bob/details'. The second path segment will be captured as the parameter 'id'.
 * * `'/user/{id}'` - Same as the previous example, but using curly brace syntax.
 * * `'/user/{id:[^/]*}'` - Same as the previous example.
 * * `'/user/{id:[0-9a-fA-F]{1,8}}'` - Similar to the previous example, but only matches if the id
 *   parameter consists of 1 to 8 hex digits.
 * * `'/files/{path:.*}'` - Matches any URL starting with '/files/' and captures the rest of the
 *   path into the parameter 'path'.
 * * `'/files/*path'` - ditto.
 * * `'/calendar/{start:date}'` - Matches "/calendar/2014-11-12" (because the pattern defined
 *   in the built-in  `date` Type matches `2014-11-12`) and provides a Date object in $stateParams.start
 *
 * @param {string} pattern  The pattern to compile into a matcher.
 * @param {Object} config  A configuration object hash:
 * @param {Object=} parentMatcher Used to concatenate the pattern/config onto
 *   an existing UrlMatcher
 *
 * * `caseInsensitive` - `true` if URL matching should be case insensitive, otherwise `false`, the default value (for backward compatibility) is `false`.
 * * `strict` - `false` if matching against a URL with a trailing slash should be treated as equivalent to a URL without a trailing slash, the default value is `true`.
 *
 * @property {string} prefix  A static prefix of this pattern. The matcher guarantees that any
 *   URL matching this matcher (i.e. any string for which {@link ui.router.util.type:UrlMatcher#methods_exec exec()} returns
 *   non-null) will start with this prefix.
 *
 * @property {string} source  The pattern that was passed into the constructor
 *
 * @property {string} sourcePath  The path portion of the source property
 *
 * @property {string} sourceSearch  The search portion of the source property
 *
 * @property {string} regex  The constructed regex that will be used to match against the url when
 *   it is time to determine which url will match.
 *
 * @returns {Object}  New `UrlMatcher` object
 */
function UrlMatcher(pattern, config, parentMatcher) {
  config = extend({ params: {} }, isObject(config) ? config : {});

  // Find all placeholders and create a compiled pattern, using either classic or curly syntax:
  //   '*' name
  //   ':' name
  //   '{' name '}'
  //   '{' name ':' regexp '}'
  // The regular expression is somewhat complicated due to the need to allow curly braces
  // inside the regular expression. The placeholder regexp breaks down as follows:
  //    ([:*])([\w\[\]]+)              - classic placeholder ($1 / $2) (search version has - for snake-case)
  //    \{([\w\[\]]+)(?:\:\s*( ... ))?\}  - curly brace placeholder ($3) with optional regexp/type ... ($4) (search version has - for snake-case
  //    (?: ... | ... | ... )+         - the regexp consists of any number of atoms, an atom being either
  //    [^{}\\]+                       - anything other than curly braces or backslash
  //    \\.                            - a backslash escape
  //    \{(?:[^{}\\]+|\\.)*\}          - a matched set of curly braces containing other atoms
  var placeholder       = /([:*])([\w\[\]]+)|\{([\w\[\]]+)(?:\:\s*((?:[^{}\\]+|\\.|\{(?:[^{}\\]+|\\.)*\})+))?\}/g,
      searchPlaceholder = /([:]?)([\w\[\].-]+)|\{([\w\[\].-]+)(?:\:\s*((?:[^{}\\]+|\\.|\{(?:[^{}\\]+|\\.)*\})+))?\}/g,
      compiled = '^', last = 0, m,
      segments = this.segments = [],
      parentParams = parentMatcher ? parentMatcher.params : {},
      params = this.params = parentMatcher ? parentMatcher.params.$$new() : new $$UMFP.ParamSet(),
      paramNames = [];

  function addParameter(id, type, config, location) {
    paramNames.push(id);
    if (parentParams[id]) return parentParams[id];
    if (!/^\w+([-.]+\w+)*(?:\[\])?$/.test(id)) throw new Error("Invalid parameter name '" + id + "' in pattern '" + pattern + "'");
    if (params[id]) throw new Error("Duplicate parameter name '" + id + "' in pattern '" + pattern + "'");
    params[id] = new $$UMFP.Param(id, type, config, location);
    return params[id];
  }

  function quoteRegExp(string, pattern, squash, optional) {
    var surroundPattern = ['',''], result = string.replace(/[\\\[\]\^$*+?.()|{}]/g, "\\$&");
    if (!pattern) return result;
    switch(squash) {
      case false: surroundPattern = ['(', ')' + (optional ? "?" : "")]; break;
      case true:
        result = result.replace(/\/$/, '');
        surroundPattern = ['(?:\/(', ')|\/)?'];
      break;
      default:    surroundPattern = ['(' + squash + "|", ')?']; break;
    }
    return result + surroundPattern[0] + pattern + surroundPattern[1];
  }

  this.source = pattern;

  // Split into static segments separated by path parameter placeholders.
  // The number of segments is always 1 more than the number of parameters.
  function matchDetails(m, isSearch) {
    var id, regexp, segment, type, cfg, arrayMode;
    id          = m[2] || m[3]; // IE[78] returns '' for unmatched groups instead of null
    cfg         = config.params[id];
    segment     = pattern.substring(last, m.index);
    regexp      = isSearch ? m[4] : m[4] || (m[1] == '*' ? '.*' : null);

    if (regexp) {
      type      = $$UMFP.type(regexp) || inherit($$UMFP.type("string"), { pattern: new RegExp(regexp, config.caseInsensitive ? 'i' : undefined) });
    }

    return {
      id: id, regexp: regexp, segment: segment, type: type, cfg: cfg
    };
  }

  var p, param, segment;
  while ((m = placeholder.exec(pattern))) {
    p = matchDetails(m, false);
    if (p.segment.indexOf('?') >= 0) break; // we're into the search part

    param = addParameter(p.id, p.type, p.cfg, "path");
    compiled += quoteRegExp(p.segment, param.type.pattern.source, param.squash, param.isOptional);
    segments.push(p.segment);
    last = placeholder.lastIndex;
  }
  segment = pattern.substring(last);

  // Find any search parameter names and remove them from the last segment
  var i = segment.indexOf('?');

  if (i >= 0) {
    var search = this.sourceSearch = segment.substring(i);
    segment = segment.substring(0, i);
    this.sourcePath = pattern.substring(0, last + i);

    if (search.length > 0) {
      last = 0;
      while ((m = searchPlaceholder.exec(search))) {
        p = matchDetails(m, true);
        param = addParameter(p.id, p.type, p.cfg, "search");
        last = placeholder.lastIndex;
        // check if ?&
      }
    }
  } else {
    this.sourcePath = pattern;
    this.sourceSearch = '';
  }

  compiled += quoteRegExp(segment) + (config.strict === false ? '\/?' : '') + '$';
  segments.push(segment);

  this.regexp = new RegExp(compiled, config.caseInsensitive ? 'i' : undefined);
  this.prefix = segments[0];
  this.$$paramNames = paramNames;
}

/**
 * @ngdoc function
 * @name ui.router.util.type:UrlMatcher#concat
 * @methodOf ui.router.util.type:UrlMatcher
 *
 * @description
 * Returns a new matcher for a pattern constructed by appending the path part and adding the
 * search parameters of the specified pattern to this pattern. The current pattern is not
 * modified. This can be understood as creating a pattern for URLs that are relative to (or
 * suffixes of) the current pattern.
 *
 * @example
 * The following two matchers are equivalent:
 * <pre>
 * new UrlMatcher('/user/{id}?q').concat('/details?date');
 * new UrlMatcher('/user/{id}/details?q&date');
 * </pre>
 *
 * @param {string} pattern  The pattern to append.
 * @param {Object} config  An object hash of the configuration for the matcher.
 * @returns {UrlMatcher}  A matcher for the concatenated pattern.
 */
UrlMatcher.prototype.concat = function (pattern, config) {
  // Because order of search parameters is irrelevant, we can add our own search
  // parameters to the end of the new pattern. Parse the new pattern by itself
  // and then join the bits together, but it's much easier to do this on a string level.
  var defaultConfig = {
    caseInsensitive: $$UMFP.caseInsensitive(),
    strict: $$UMFP.strictMode(),
    squash: $$UMFP.defaultSquashPolicy()
  };
  return new UrlMatcher(this.sourcePath + pattern + this.sourceSearch, extend(defaultConfig, config), this);
};

UrlMatcher.prototype.toString = function () {
  return this.source;
};

/**
 * @ngdoc function
 * @name ui.router.util.type:UrlMatcher#exec
 * @methodOf ui.router.util.type:UrlMatcher
 *
 * @description
 * Tests the specified path against this matcher, and returns an object containing the captured
 * parameter values, or null if the path does not match. The returned object contains the values
 * of any search parameters that are mentioned in the pattern, but their value may be null if
 * they are not present in `searchParams`. This means that search parameters are always treated
 * as optional.
 *
 * @example
 * <pre>
 * new UrlMatcher('/user/{id}?q&r').exec('/user/bob', {
 *   x: '1', q: 'hello'
 * });
 * // returns { id: 'bob', q: 'hello', r: null }
 * </pre>
 *
 * @param {string} path  The URL path to match, e.g. `$location.path()`.
 * @param {Object} searchParams  URL search parameters, e.g. `$location.search()`.
 * @returns {Object}  The captured parameter values.
 */
UrlMatcher.prototype.exec = function (path, searchParams) {
  var m = this.regexp.exec(path);
  if (!m) return null;
  searchParams = searchParams || {};

  var paramNames = this.parameters(), nTotal = paramNames.length,
    nPath = this.segments.length - 1,
    values = {}, i, j, cfg, paramName;

  if (nPath !== m.length - 1) throw new Error("Unbalanced capture group in route '" + this.source + "'");

  function decodePathArray(string) {
    function reverseString(str) { return str.split("").reverse().join(""); }
    function unquoteDashes(str) { return str.replace(/\\-/g, "-"); }

    var split = reverseString(string).split(/-(?!\\)/);
    var allReversed = map(split, reverseString);
    return map(allReversed, unquoteDashes).reverse();
  }

  var param, paramVal;
  for (i = 0; i < nPath; i++) {
    paramName = paramNames[i];
    param = this.params[paramName];
    paramVal = m[i+1];
    // if the param value matches a pre-replace pair, replace the value before decoding.
    for (j = 0; j < param.replace.length; j++) {
      if (param.replace[j].from === paramVal) paramVal = param.replace[j].to;
    }
    if (paramVal && param.array === true) paramVal = decodePathArray(paramVal);
    if (isDefined(paramVal)) paramVal = param.type.decode(paramVal);
    values[paramName] = param.value(paramVal);
  }
  for (/**/; i < nTotal; i++) {
    paramName = paramNames[i];
    values[paramName] = this.params[paramName].value(searchParams[paramName]);
    param = this.params[paramName];
    paramVal = searchParams[paramName];
    for (j = 0; j < param.replace.length; j++) {
      if (param.replace[j].from === paramVal) paramVal = param.replace[j].to;
    }
    if (isDefined(paramVal)) paramVal = param.type.decode(paramVal);
    values[paramName] = param.value(paramVal);
  }

  return values;
};

/**
 * @ngdoc function
 * @name ui.router.util.type:UrlMatcher#parameters
 * @methodOf ui.router.util.type:UrlMatcher
 *
 * @description
 * Returns the names of all path and search parameters of this pattern in an unspecified order.
 *
 * @returns {Array.<string>}  An array of parameter names. Must be treated as read-only. If the
 *    pattern has no parameters, an empty array is returned.
 */
UrlMatcher.prototype.parameters = function (param) {
  if (!isDefined(param)) return this.$$paramNames;
  return this.params[param] || null;
};

/**
 * @ngdoc function
 * @name ui.router.util.type:UrlMatcher#validates
 * @methodOf ui.router.util.type:UrlMatcher
 *
 * @description
 * Checks an object hash of parameters to validate their correctness according to the parameter
 * types of this `UrlMatcher`.
 *
 * @param {Object} params The object hash of parameters to validate.
 * @returns {boolean} Returns `true` if `params` validates, otherwise `false`.
 */
UrlMatcher.prototype.validates = function (params) {
  return this.params.$$validates(params);
};

/**
 * @ngdoc function
 * @name ui.router.util.type:UrlMatcher#format
 * @methodOf ui.router.util.type:UrlMatcher
 *
 * @description
 * Creates a URL that matches this pattern by substituting the specified values
 * for the path and search parameters. Null values for path parameters are
 * treated as empty strings.
 *
 * @example
 * <pre>
 * new UrlMatcher('/user/{id}?q').format({ id:'bob', q:'yes' });
 * // returns '/user/bob?q=yes'
 * </pre>
 *
 * @param {Object} values  the values to substitute for the parameters in this pattern.
 * @returns {string}  the formatted URL (path and optionally search part).
 */
UrlMatcher.prototype.format = function (values) {
  values = values || {};
  var segments = this.segments, params = this.parameters(), paramset = this.params;
  if (!this.validates(values)) return null;

  var i, search = false, nPath = segments.length - 1, nTotal = params.length, result = segments[0];

  function encodeDashes(str) { // Replace dashes with encoded "\-"
    return encodeURIComponent(str).replace(/-/g, function(c) { return '%5C%' + c.charCodeAt(0).toString(16).toUpperCase(); });
  }

  for (i = 0; i < nTotal; i++) {
    var isPathParam = i < nPath;
    var name = params[i], param = paramset[name], value = param.value(values[name]);
    var isDefaultValue = param.isOptional && param.type.equals(param.value(), value);
    var squash = isDefaultValue ? param.squash : false;
    var encoded = param.type.encode(value);

    if (isPathParam) {
      var nextSegment = segments[i + 1];
      var isFinalPathParam = i + 1 === nPath;

      if (squash === false) {
        if (encoded != null) {
          if (isArray(encoded)) {
            result += map(encoded, encodeDashes).join("-");
          } else {
            result += encodeURIComponent(encoded);
          }
        }
        result += nextSegment;
      } else if (squash === true) {
        var capture = result.match(/\/$/) ? /\/?(.*)/ : /(.*)/;
        result += nextSegment.match(capture)[1];
      } else if (isString(squash)) {
        result += squash + nextSegment;
      }

      if (isFinalPathParam && param.squash === true && result.slice(-1) === '/') result = result.slice(0, -1);
    } else {
      if (encoded == null || (isDefaultValue && squash !== false)) continue;
      if (!isArray(encoded)) encoded = [ encoded ];
      if (encoded.length === 0) continue;
      encoded = map(encoded, encodeURIComponent).join('&' + name + '=');
      result += (search ? '&' : '?') + (name + '=' + encoded);
      search = true;
    }
  }

  return result;
};

/**
 * @ngdoc object
 * @name ui.router.util.type:Type
 *
 * @description
 * Implements an interface to define custom parameter types that can be decoded from and encoded to
 * string parameters matched in a URL. Used by {@link ui.router.util.type:UrlMatcher `UrlMatcher`}
 * objects when matching or formatting URLs, or comparing or validating parameter values.
 *
 * See {@link ui.router.util.$urlMatcherFactory#methods_type `$urlMatcherFactory#type()`} for more
 * information on registering custom types.
 *
 * @param {Object} config  A configuration object which contains the custom type definition.  The object's
 *        properties will override the default methods and/or pattern in `Type`'s public interface.
 * @example
 * <pre>
 * {
 *   decode: function(val) { return parseInt(val, 10); },
 *   encode: function(val) { return val && val.toString(); },
 *   equals: function(a, b) { return this.is(a) && a === b; },
 *   is: function(val) { return angular.isNumber(val) isFinite(val) && val % 1 === 0; },
 *   pattern: /\d+/
 * }
 * </pre>
 *
 * @property {RegExp} pattern The regular expression pattern used to match values of this type when
 *           coming from a substring of a URL.
 *
 * @returns {Object}  Returns a new `Type` object.
 */
function Type(config) {
  extend(this, config);
}

/**
 * @ngdoc function
 * @name ui.router.util.type:Type#is
 * @methodOf ui.router.util.type:Type
 *
 * @description
 * Detects whether a value is of a particular type. Accepts a native (decoded) value
 * and determines whether it matches the current `Type` object.
 *
 * @param {*} val  The value to check.
 * @param {string} key  Optional. If the type check is happening in the context of a specific
 *        {@link ui.router.util.type:UrlMatcher `UrlMatcher`} object, this is the name of the
 *        parameter in which `val` is stored. Can be used for meta-programming of `Type` objects.
 * @returns {Boolean}  Returns `true` if the value matches the type, otherwise `false`.
 */
Type.prototype.is = function(val, key) {
  return true;
};

/**
 * @ngdoc function
 * @name ui.router.util.type:Type#encode
 * @methodOf ui.router.util.type:Type
 *
 * @description
 * Encodes a custom/native type value to a string that can be embedded in a URL. Note that the
 * return value does *not* need to be URL-safe (i.e. passed through `encodeURIComponent()`), it
 * only needs to be a representation of `val` that has been coerced to a string.
 *
 * @param {*} val  The value to encode.
 * @param {string} key  The name of the parameter in which `val` is stored. Can be used for
 *        meta-programming of `Type` objects.
 * @returns {string}  Returns a string representation of `val` that can be encoded in a URL.
 */
Type.prototype.encode = function(val, key) {
  return val;
};

/**
 * @ngdoc function
 * @name ui.router.util.type:Type#decode
 * @methodOf ui.router.util.type:Type
 *
 * @description
 * Converts a parameter value (from URL string or transition param) to a custom/native value.
 *
 * @param {string} val  The URL parameter value to decode.
 * @param {string} key  The name of the parameter in which `val` is stored. Can be used for
 *        meta-programming of `Type` objects.
 * @returns {*}  Returns a custom representation of the URL parameter value.
 */
Type.prototype.decode = function(val, key) {
  return val;
};

/**
 * @ngdoc function
 * @name ui.router.util.type:Type#equals
 * @methodOf ui.router.util.type:Type
 *
 * @description
 * Determines whether two decoded values are equivalent.
 *
 * @param {*} a  A value to compare against.
 * @param {*} b  A value to compare against.
 * @returns {Boolean}  Returns `true` if the values are equivalent/equal, otherwise `false`.
 */
Type.prototype.equals = function(a, b) {
  return a == b;
};

Type.prototype.$subPattern = function() {
  var sub = this.pattern.toString();
  return sub.substr(1, sub.length - 2);
};

Type.prototype.pattern = /.*/;

Type.prototype.toString = function() { return "{Type:" + this.name + "}"; };

/** Given an encoded string, or a decoded object, returns a decoded object */
Type.prototype.$normalize = function(val) {
  return this.is(val) ? val : this.decode(val);
};

/*
 * Wraps an existing custom Type as an array of Type, depending on 'mode'.
 * e.g.:
 * - urlmatcher pattern "/path?{queryParam[]:int}"
 * - url: "/path?queryParam=1&queryParam=2
 * - $stateParams.queryParam will be [1, 2]
 * if `mode` is "auto", then
 * - url: "/path?queryParam=1 will create $stateParams.queryParam: 1
 * - url: "/path?queryParam=1&queryParam=2 will create $stateParams.queryParam: [1, 2]
 */
Type.prototype.$asArray = function(mode, isSearch) {
  if (!mode) return this;
  if (mode === "auto" && !isSearch) throw new Error("'auto' array mode is for query parameters only");

  function ArrayType(type, mode) {
    function bindTo(type, callbackName) {
      return function() {
        return type[callbackName].apply(type, arguments);
      };
    }

    // Wrap non-array value as array
    function arrayWrap(val) { return isArray(val) ? val : (isDefined(val) ? [ val ] : []); }
    // Unwrap array value for "auto" mode. Return undefined for empty array.
    function arrayUnwrap(val) {
      switch(val.length) {
        case 0: return undefined;
        case 1: return mode === "auto" ? val[0] : val;
        default: return val;
      }
    }
    function falsey(val) { return !val; }

    // Wraps type (.is/.encode/.decode) functions to operate on each value of an array
    function arrayHandler(callback, allTruthyMode) {
      return function handleArray(val) {
        if (isArray(val) && val.length === 0) return val;
        val = arrayWrap(val);
        var result = map(val, callback);
        if (allTruthyMode === true)
          return filter(result, falsey).length === 0;
        return arrayUnwrap(result);
      };
    }

    // Wraps type (.equals) functions to operate on each value of an array
    function arrayEqualsHandler(callback) {
      return function handleArray(val1, val2) {
        var left = arrayWrap(val1), right = arrayWrap(val2);
        if (left.length !== right.length) return false;
        for (var i = 0; i < left.length; i++) {
          if (!callback(left[i], right[i])) return false;
        }
        return true;
      };
    }

    this.encode = arrayHandler(bindTo(type, 'encode'));
    this.decode = arrayHandler(bindTo(type, 'decode'));
    this.is     = arrayHandler(bindTo(type, 'is'), true);
    this.equals = arrayEqualsHandler(bindTo(type, 'equals'));
    this.pattern = type.pattern;
    this.$normalize = arrayHandler(bindTo(type, '$normalize'));
    this.name = type.name;
    this.$arrayMode = mode;
  }

  return new ArrayType(this, mode);
};



/**
 * @ngdoc object
 * @name ui.router.util.$urlMatcherFactory
 *
 * @description
 * Factory for {@link ui.router.util.type:UrlMatcher `UrlMatcher`} instances. The factory
 * is also available to providers under the name `$urlMatcherFactoryProvider`.
 */
function $UrlMatcherFactory() {
  $$UMFP = this;

  var isCaseInsensitive = false, isStrictMode = true, defaultSquashPolicy = false;

  // Use tildes to pre-encode slashes.
  // If the slashes are simply URLEncoded, the browser can choose to pre-decode them,
  // and bidirectional encoding/decoding fails.
  // Tilde was chosen because it's not a RFC 3986 section 2.2 Reserved Character
  function valToString(val) { return val != null ? val.toString().replace(/~/g, "~~").replace(/\//g, "~2F") : val; }
  function valFromString(val) { return val != null ? val.toString().replace(/~2F/g, "/").replace(/~~/g, "~") : val; }

  var $types = {}, enqueue = true, typeQueue = [], injector, defaultTypes = {
    "string": {
      encode: valToString,
      decode: valFromString,
      // TODO: in 1.0, make string .is() return false if value is undefined/null by default.
      // In 0.2.x, string params are optional by default for backwards compat
      is: function(val) { return val == null || !isDefined(val) || typeof val === "string"; },
      pattern: /[^/]*/
    },
    "int": {
      encode: valToString,
      decode: function(val) { return parseInt(val, 10); },
      is: function(val) { return isDefined(val) && this.decode(val.toString()) === val; },
      pattern: /\d+/
    },
    "bool": {
      encode: function(val) { return val ? 1 : 0; },
      decode: function(val) { return parseInt(val, 10) !== 0; },
      is: function(val) { return val === true || val === false; },
      pattern: /0|1/
    },
    "date": {
      encode: function (val) {
        if (!this.is(val))
          return undefined;
        return [ val.getFullYear(),
          ('0' + (val.getMonth() + 1)).slice(-2),
          ('0' + val.getDate()).slice(-2)
        ].join("-");
      },
      decode: function (val) {
        if (this.is(val)) return val;
        var match = this.capture.exec(val);
        return match ? new Date(match[1], match[2] - 1, match[3]) : undefined;
      },
      is: function(val) { return val instanceof Date && !isNaN(val.valueOf()); },
      equals: function (a, b) { return this.is(a) && this.is(b) && a.toISOString() === b.toISOString(); },
      pattern: /[0-9]{4}-(?:0[1-9]|1[0-2])-(?:0[1-9]|[1-2][0-9]|3[0-1])/,
      capture: /([0-9]{4})-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])/
    },
    "json": {
      encode: angular.toJson,
      decode: angular.fromJson,
      is: angular.isObject,
      equals: angular.equals,
      pattern: /[^/]*/
    },
    "any": { // does not encode/decode
      encode: angular.identity,
      decode: angular.identity,
      equals: angular.equals,
      pattern: /.*/
    }
  };

  function getDefaultConfig() {
    return {
      strict: isStrictMode,
      caseInsensitive: isCaseInsensitive
    };
  }

  function isInjectable(value) {
    return (isFunction(value) || (isArray(value) && isFunction(value[value.length - 1])));
  }

  /**
   * [Internal] Get the default value of a parameter, which may be an injectable function.
   */
  $UrlMatcherFactory.$$getDefaultValue = function(config) {
    if (!isInjectable(config.value)) return config.value;
    if (!injector) throw new Error("Injectable functions cannot be called at configuration time");
    return injector.invoke(config.value);
  };

  /**
   * @ngdoc function
   * @name ui.router.util.$urlMatcherFactory#caseInsensitive
   * @methodOf ui.router.util.$urlMatcherFactory
   *
   * @description
   * Defines whether URL matching should be case sensitive (the default behavior), or not.
   *
   * @param {boolean} value `false` to match URL in a case sensitive manner; otherwise `true`;
   * @returns {boolean} the current value of caseInsensitive
   */
  this.caseInsensitive = function(value) {
    if (isDefined(value))
      isCaseInsensitive = value;
    return isCaseInsensitive;
  };

  /**
   * @ngdoc function
   * @name ui.router.util.$urlMatcherFactory#strictMode
   * @methodOf ui.router.util.$urlMatcherFactory
   *
   * @description
   * Defines whether URLs should match trailing slashes, or not (the default behavior).
   *
   * @param {boolean=} value `false` to match trailing slashes in URLs, otherwise `true`.
   * @returns {boolean} the current value of strictMode
   */
  this.strictMode = function(value) {
    if (isDefined(value))
      isStrictMode = value;
    return isStrictMode;
  };

  /**
   * @ngdoc function
   * @name ui.router.util.$urlMatcherFactory#defaultSquashPolicy
   * @methodOf ui.router.util.$urlMatcherFactory
   *
   * @description
   * Sets the default behavior when generating or matching URLs with default parameter values.
   *
   * @param {string} value A string that defines the default parameter URL squashing behavior.
   *    `nosquash`: When generating an href with a default parameter value, do not squash the parameter value from the URL
   *    `slash`: When generating an href with a default parameter value, squash (remove) the parameter value, and, if the
   *             parameter is surrounded by slashes, squash (remove) one slash from the URL
   *    any other string, e.g. "~": When generating an href with a default parameter value, squash (remove)
   *             the parameter value from the URL and replace it with this string.
   */
  this.defaultSquashPolicy = function(value) {
    if (!isDefined(value)) return defaultSquashPolicy;
    if (value !== true && value !== false && !isString(value))
      throw new Error("Invalid squash policy: " + value + ". Valid policies: false, true, arbitrary-string");
    defaultSquashPolicy = value;
    return value;
  };

  /**
   * @ngdoc function
   * @name ui.router.util.$urlMatcherFactory#compile
   * @methodOf ui.router.util.$urlMatcherFactory
   *
   * @description
   * Creates a {@link ui.router.util.type:UrlMatcher `UrlMatcher`} for the specified pattern.
   *
   * @param {string} pattern  The URL pattern.
   * @param {Object} config  The config object hash.
   * @returns {UrlMatcher}  The UrlMatcher.
   */
  this.compile = function (pattern, config) {
    return new UrlMatcher(pattern, extend(getDefaultConfig(), config));
  };

  /**
   * @ngdoc function
   * @name ui.router.util.$urlMatcherFactory#isMatcher
   * @methodOf ui.router.util.$urlMatcherFactory
   *
   * @description
   * Returns true if the specified object is a `UrlMatcher`, or false otherwise.
   *
   * @param {Object} object  The object to perform the type check against.
   * @returns {Boolean}  Returns `true` if the object matches the `UrlMatcher` interface, by
   *          implementing all the same methods.
   */
  this.isMatcher = function (o) {
    if (!isObject(o)) return false;
    var result = true;

    forEach(UrlMatcher.prototype, function(val, name) {
      if (isFunction(val)) {
        result = result && (isDefined(o[name]) && isFunction(o[name]));
      }
    });
    return result;
  };

  /**
   * @ngdoc function
   * @name ui.router.util.$urlMatcherFactory#type
   * @methodOf ui.router.util.$urlMatcherFactory
   *
   * @description
   * Registers a custom {@link ui.router.util.type:Type `Type`} object that can be used to
   * generate URLs with typed parameters.
   *
   * @param {string} name  The type name.
   * @param {Object|Function} definition   The type definition. See
   *        {@link ui.router.util.type:Type `Type`} for information on the values accepted.
   * @param {Object|Function} definitionFn (optional) A function that is injected before the app
   *        runtime starts.  The result of this function is merged into the existing `definition`.
   *        See {@link ui.router.util.type:Type `Type`} for information on the values accepted.
   *
   * @returns {Object}  Returns `$urlMatcherFactoryProvider`.
   *
   * @example
   * This is a simple example of a custom type that encodes and decodes items from an
   * array, using the array index as the URL-encoded value:
   *
   * <pre>
   * var list = ['John', 'Paul', 'George', 'Ringo'];
   *
   * $urlMatcherFactoryProvider.type('listItem', {
   *   encode: function(item) {
   *     // Represent the list item in the URL using its corresponding index
   *     return list.indexOf(item);
   *   },
   *   decode: function(item) {
   *     // Look up the list item by index
   *     return list[parseInt(item, 10)];
   *   },
   *   is: function(item) {
   *     // Ensure the item is valid by checking to see that it appears
   *     // in the list
   *     return list.indexOf(item) > -1;
   *   }
   * });
   *
   * $stateProvider.state('list', {
   *   url: "/list/{item:listItem}",
   *   controller: function($scope, $stateParams) {
   *     console.log($stateParams.item);
   *   }
   * });
   *
   * // ...
   *
   * // Changes URL to '/list/3', logs "Ringo" to the console
   * $state.go('list', { item: "Ringo" });
   * </pre>
   *
   * This is a more complex example of a type that relies on dependency injection to
   * interact with services, and uses the parameter name from the URL to infer how to
   * handle encoding and decoding parameter values:
   *
   * <pre>
   * // Defines a custom type that gets a value from a service,
   * // where each service gets different types of values from
   * // a backend API:
   * $urlMatcherFactoryProvider.type('dbObject', {}, function(Users, Posts) {
   *
   *   // Matches up services to URL parameter names
   *   var services = {
   *     user: Users,
   *     post: Posts
   *   };
   *
   *   return {
   *     encode: function(object) {
   *       // Represent the object in the URL using its unique ID
   *       return object.id;
   *     },
   *     decode: function(value, key) {
   *       // Look up the object by ID, using the parameter
   *       // name (key) to call the correct service
   *       return services[key].findById(value);
   *     },
   *     is: function(object, key) {
   *       // Check that object is a valid dbObject
   *       return angular.isObject(object) && object.id && services[key];
   *     }
   *     equals: function(a, b) {
   *       // Check the equality of decoded objects by comparing
   *       // their unique IDs
   *       return a.id === b.id;
   *     }
   *   };
   * });
   *
   * // In a config() block, you can then attach URLs with
   * // type-annotated parameters:
   * $stateProvider.state('users', {
   *   url: "/users",
   *   // ...
   * }).state('users.item', {
   *   url: "/{user:dbObject}",
   *   controller: function($scope, $stateParams) {
   *     // $stateParams.user will now be an object returned from
   *     // the Users service
   *   },
   *   // ...
   * });
   * </pre>
   */
  this.type = function (name, definition, definitionFn) {
    if (!isDefined(definition)) return $types[name];
    if ($types.hasOwnProperty(name)) throw new Error("A type named '" + name + "' has already been defined.");

    $types[name] = new Type(extend({ name: name }, definition));
    if (definitionFn) {
      typeQueue.push({ name: name, def: definitionFn });
      if (!enqueue) flushTypeQueue();
    }
    return this;
  };

  // `flushTypeQueue()` waits until `$urlMatcherFactory` is injected before invoking the queued `definitionFn`s
  function flushTypeQueue() {
    while(typeQueue.length) {
      var type = typeQueue.shift();
      if (type.pattern) throw new Error("You cannot override a type's .pattern at runtime.");
      angular.extend($types[type.name], injector.invoke(type.def));
    }
  }

  // Register default types. Store them in the prototype of $types.
  forEach(defaultTypes, function(type, name) { $types[name] = new Type(extend({name: name}, type)); });
  $types = inherit($types, {});

  /* No need to document $get, since it returns this */
  this.$get = ['$injector', function ($injector) {
    injector = $injector;
    enqueue = false;
    flushTypeQueue();

    forEach(defaultTypes, function(type, name) {
      if (!$types[name]) $types[name] = new Type(type);
    });
    return this;
  }];

  this.Param = function Param(id, type, config, location) {
    var self = this;
    config = unwrapShorthand(config);
    type = getType(config, type, location);
    var arrayMode = getArrayMode();
    type = arrayMode ? type.$asArray(arrayMode, location === "search") : type;
    if (type.name === "string" && !arrayMode && location === "path" && config.value === undefined)
      config.value = ""; // for 0.2.x; in 0.3.0+ do not automatically default to ""
    var isOptional = config.value !== undefined;
    var squash = getSquashPolicy(config, isOptional);
    var replace = getReplace(config, arrayMode, isOptional, squash);

    function unwrapShorthand(config) {
      var keys = isObject(config) ? objectKeys(config) : [];
      var isShorthand = indexOf(keys, "value") === -1 && indexOf(keys, "type") === -1 &&
                        indexOf(keys, "squash") === -1 && indexOf(keys, "array") === -1;
      if (isShorthand) config = { value: config };
      config.$$fn = isInjectable(config.value) ? config.value : function () { return config.value; };
      return config;
    }

    function getType(config, urlType, location) {
      if (config.type && urlType) throw new Error("Param '"+id+"' has two type configurations.");
      if (urlType) return urlType;
      if (!config.type) return (location === "config" ? $types.any : $types.string);

      if (angular.isString(config.type))
        return $types[config.type];
      if (config.type instanceof Type)
        return config.type;
      return new Type(config.type);
    }

    // array config: param name (param[]) overrides default settings.  explicit config overrides param name.
    function getArrayMode() {
      var arrayDefaults = { array: (location === "search" ? "auto" : false) };
      var arrayParamNomenclature = id.match(/\[\]$/) ? { array: true } : {};
      return extend(arrayDefaults, arrayParamNomenclature, config).array;
    }

    /**
     * returns false, true, or the squash value to indicate the "default parameter url squash policy".
     */
    function getSquashPolicy(config, isOptional) {
      var squash = config.squash;
      if (!isOptional || squash === false) return false;
      if (!isDefined(squash) || squash == null) return defaultSquashPolicy;
      if (squash === true || isString(squash)) return squash;
      throw new Error("Invalid squash policy: '" + squash + "'. Valid policies: false, true, or arbitrary string");
    }

    function getReplace(config, arrayMode, isOptional, squash) {
      var replace, configuredKeys, defaultPolicy = [
        { from: "",   to: (isOptional || arrayMode ? undefined : "") },
        { from: null, to: (isOptional || arrayMode ? undefined : "") }
      ];
      replace = isArray(config.replace) ? config.replace : [];
      if (isString(squash))
        replace.push({ from: squash, to: undefined });
      configuredKeys = map(replace, function(item) { return item.from; } );
      return filter(defaultPolicy, function(item) { return indexOf(configuredKeys, item.from) === -1; }).concat(replace);
    }

    /**
     * [Internal] Get the default value of a parameter, which may be an injectable function.
     */
    function $$getDefaultValue() {
      if (!injector) throw new Error("Injectable functions cannot be called at configuration time");
      var defaultValue = injector.invoke(config.$$fn);
      if (defaultValue !== null && defaultValue !== undefined && !self.type.is(defaultValue))
        throw new Error("Default value (" + defaultValue + ") for parameter '" + self.id + "' is not an instance of Type (" + self.type.name + ")");
      return defaultValue;
    }

    /**
     * [Internal] Gets the decoded representation of a value if the value is defined, otherwise, returns the
     * default value, which may be the result of an injectable function.
     */
    function $value(value) {
      function hasReplaceVal(val) { return function(obj) { return obj.from === val; }; }
      function $replace(value) {
        var replacement = map(filter(self.replace, hasReplaceVal(value)), function(obj) { return obj.to; });
        return replacement.length ? replacement[0] : value;
      }
      value = $replace(value);
      return !isDefined(value) ? $$getDefaultValue() : self.type.$normalize(value);
    }

    function toString() { return "{Param:" + id + " " + type + " squash: '" + squash + "' optional: " + isOptional + "}"; }

    extend(this, {
      id: id,
      type: type,
      location: location,
      array: arrayMode,
      squash: squash,
      replace: replace,
      isOptional: isOptional,
      value: $value,
      dynamic: undefined,
      config: config,
      toString: toString
    });
  };

  function ParamSet(params) {
    extend(this, params || {});
  }

  ParamSet.prototype = {
    $$new: function() {
      return inherit(this, extend(new ParamSet(), { $$parent: this}));
    },
    $$keys: function () {
      var keys = [], chain = [], parent = this,
        ignore = objectKeys(ParamSet.prototype);
      while (parent) { chain.push(parent); parent = parent.$$parent; }
      chain.reverse();
      forEach(chain, function(paramset) {
        forEach(objectKeys(paramset), function(key) {
            if (indexOf(keys, key) === -1 && indexOf(ignore, key) === -1) keys.push(key);
        });
      });
      return keys;
    },
    $$values: function(paramValues) {
      var values = {}, self = this;
      forEach(self.$$keys(), function(key) {
        values[key] = self[key].value(paramValues && paramValues[key]);
      });
      return values;
    },
    $$equals: function(paramValues1, paramValues2) {
      var equal = true, self = this;
      forEach(self.$$keys(), function(key) {
        var left = paramValues1 && paramValues1[key], right = paramValues2 && paramValues2[key];
        if (!self[key].type.equals(left, right)) equal = false;
      });
      return equal;
    },
    $$validates: function $$validate(paramValues) {
      var keys = this.$$keys(), i, param, rawVal, normalized, encoded;
      for (i = 0; i < keys.length; i++) {
        param = this[keys[i]];
        rawVal = paramValues[keys[i]];
        if ((rawVal === undefined || rawVal === null) && param.isOptional)
          break; // There was no parameter value, but the param is optional
        normalized = param.type.$normalize(rawVal);
        if (!param.type.is(normalized))
          return false; // The value was not of the correct Type, and could not be decoded to the correct Type
        encoded = param.type.encode(normalized);
        if (angular.isString(encoded) && !param.type.pattern.exec(encoded))
          return false; // The value was of the correct type, but when encoded, did not match the Type's regexp
      }
      return true;
    },
    $$parent: undefined
  };

  this.ParamSet = ParamSet;
}

// Register as a provider so it's available to other providers
angular.module('ui.router.util').provider('$urlMatcherFactory', $UrlMatcherFactory);
angular.module('ui.router.util').run(['$urlMatcherFactory', function($urlMatcherFactory) { }]);

/**
 * @ngdoc object
 * @name ui.router.router.$urlRouterProvider
 *
 * @requires ui.router.util.$urlMatcherFactoryProvider
 * @requires $locationProvider
 *
 * @description
 * `$urlRouterProvider` has the responsibility of watching `$location`. 
 * When `$location` changes it runs through a list of rules one by one until a 
 * match is found. `$urlRouterProvider` is used behind the scenes anytime you specify 
 * a url in a state configuration. All urls are compiled into a UrlMatcher object.
 *
 * There are several methods on `$urlRouterProvider` that make it useful to use directly
 * in your module config.
 */
$UrlRouterProvider.$inject = ['$locationProvider', '$urlMatcherFactoryProvider'];
function $UrlRouterProvider(   $locationProvider,   $urlMatcherFactory) {
  var rules = [], otherwise = null, interceptDeferred = false, listener;

  // Returns a string that is a prefix of all strings matching the RegExp
  function regExpPrefix(re) {
    var prefix = /^\^((?:\\[^a-zA-Z0-9]|[^\\\[\]\^$*+?.()|{}]+)*)/.exec(re.source);
    return (prefix != null) ? prefix[1].replace(/\\(.)/g, "$1") : '';
  }

  // Interpolates matched values into a String.replace()-style pattern
  function interpolate(pattern, match) {
    return pattern.replace(/\$(\$|\d{1,2})/, function (m, what) {
      return match[what === '$' ? 0 : Number(what)];
    });
  }

  /**
   * @ngdoc function
   * @name ui.router.router.$urlRouterProvider#rule
   * @methodOf ui.router.router.$urlRouterProvider
   *
   * @description
   * Defines rules that are used by `$urlRouterProvider` to find matches for
   * specific URLs.
   *
   * @example
   * <pre>
   * var app = angular.module('app', ['ui.router.router']);
   *
   * app.config(function ($urlRouterProvider) {
   *   // Here's an example of how you might allow case insensitive urls
   *   $urlRouterProvider.rule(function ($injector, $location) {
   *     var path = $location.path(),
   *         normalized = path.toLowerCase();
   *
   *     if (path !== normalized) {
   *       return normalized;
   *     }
   *   });
   * });
   * </pre>
   *
   * @param {function} rule Handler function that takes `$injector` and `$location`
   * services as arguments. You can use them to return a valid path as a string.
   *
   * @return {object} `$urlRouterProvider` - `$urlRouterProvider` instance
   */
  this.rule = function (rule) {
    if (!isFunction(rule)) throw new Error("'rule' must be a function");
    rules.push(rule);
    return this;
  };

  /**
   * @ngdoc object
   * @name ui.router.router.$urlRouterProvider#otherwise
   * @methodOf ui.router.router.$urlRouterProvider
   *
   * @description
   * Defines a path that is used when an invalid route is requested.
   *
   * @example
   * <pre>
   * var app = angular.module('app', ['ui.router.router']);
   *
   * app.config(function ($urlRouterProvider) {
   *   // if the path doesn't match any of the urls you configured
   *   // otherwise will take care of routing the user to the
   *   // specified url
   *   $urlRouterProvider.otherwise('/index');
   *
   *   // Example of using function rule as param
   *   $urlRouterProvider.otherwise(function ($injector, $location) {
   *     return '/a/valid/url';
   *   });
   * });
   * </pre>
   *
   * @param {string|function} rule The url path you want to redirect to or a function 
   * rule that returns the url path. The function version is passed two params: 
   * `$injector` and `$location` services, and must return a url string.
   *
   * @return {object} `$urlRouterProvider` - `$urlRouterProvider` instance
   */
  this.otherwise = function (rule) {
    if (isString(rule)) {
      var redirect = rule;
      rule = function () { return redirect; };
    }
    else if (!isFunction(rule)) throw new Error("'rule' must be a function");
    otherwise = rule;
    return this;
  };


  function handleIfMatch($injector, handler, match) {
    if (!match) return false;
    var result = $injector.invoke(handler, handler, { $match: match });
    return isDefined(result) ? result : true;
  }

  /**
   * @ngdoc function
   * @name ui.router.router.$urlRouterProvider#when
   * @methodOf ui.router.router.$urlRouterProvider
   *
   * @description
   * Registers a handler for a given url matching. 
   * 
   * If the handler is a string, it is
   * treated as a redirect, and is interpolated according to the syntax of match
   * (i.e. like `String.replace()` for `RegExp`, or like a `UrlMatcher` pattern otherwise).
   *
   * If the handler is a function, it is injectable. It gets invoked if `$location`
   * matches. You have the option of inject the match object as `$match`.
   *
   * The handler can return
   *
   * - **falsy** to indicate that the rule didn't match after all, then `$urlRouter`
   *   will continue trying to find another one that matches.
   * - **string** which is treated as a redirect and passed to `$location.url()`
   * - **void** or any **truthy** value tells `$urlRouter` that the url was handled.
   *
   * @example
   * <pre>
   * var app = angular.module('app', ['ui.router.router']);
   *
   * app.config(function ($urlRouterProvider) {
   *   $urlRouterProvider.when($state.url, function ($match, $stateParams) {
   *     if ($state.$current.navigable !== state ||
   *         !equalForKeys($match, $stateParams) {
   *      $state.transitionTo(state, $match, false);
   *     }
   *   });
   * });
   * </pre>
   *
   * @param {string|object} what The incoming path that you want to redirect.
   * @param {string|function} handler The path you want to redirect your user to.
   */
  this.when = function (what, handler) {
    var redirect, handlerIsString = isString(handler);
    if (isString(what)) what = $urlMatcherFactory.compile(what);

    if (!handlerIsString && !isFunction(handler) && !isArray(handler))
      throw new Error("invalid 'handler' in when()");

    var strategies = {
      matcher: function (what, handler) {
        if (handlerIsString) {
          redirect = $urlMatcherFactory.compile(handler);
          handler = ['$match', function ($match) { return redirect.format($match); }];
        }
        return extend(function ($injector, $location) {
          return handleIfMatch($injector, handler, what.exec($location.path(), $location.search()));
        }, {
          prefix: isString(what.prefix) ? what.prefix : ''
        });
      },
      regex: function (what, handler) {
        if (what.global || what.sticky) throw new Error("when() RegExp must not be global or sticky");

        if (handlerIsString) {
          redirect = handler;
          handler = ['$match', function ($match) { return interpolate(redirect, $match); }];
        }
        return extend(function ($injector, $location) {
          return handleIfMatch($injector, handler, what.exec($location.path()));
        }, {
          prefix: regExpPrefix(what)
        });
      }
    };

    var check = { matcher: $urlMatcherFactory.isMatcher(what), regex: what instanceof RegExp };

    for (var n in check) {
      if (check[n]) return this.rule(strategies[n](what, handler));
    }

    throw new Error("invalid 'what' in when()");
  };

  /**
   * @ngdoc function
   * @name ui.router.router.$urlRouterProvider#deferIntercept
   * @methodOf ui.router.router.$urlRouterProvider
   *
   * @description
   * Disables (or enables) deferring location change interception.
   *
   * If you wish to customize the behavior of syncing the URL (for example, if you wish to
   * defer a transition but maintain the current URL), call this method at configuration time.
   * Then, at run time, call `$urlRouter.listen()` after you have configured your own
   * `$locationChangeSuccess` event handler.
   *
   * @example
   * <pre>
   * var app = angular.module('app', ['ui.router.router']);
   *
   * app.config(function ($urlRouterProvider) {
   *
   *   // Prevent $urlRouter from automatically intercepting URL changes;
   *   // this allows you to configure custom behavior in between
   *   // location changes and route synchronization:
   *   $urlRouterProvider.deferIntercept();
   *
   * }).run(function ($rootScope, $urlRouter, UserService) {
   *
   *   $rootScope.$on('$locationChangeSuccess', function(e) {
   *     // UserService is an example service for managing user state
   *     if (UserService.isLoggedIn()) return;
   *
   *     // Prevent $urlRouter's default handler from firing
   *     e.preventDefault();
   *
   *     UserService.handleLogin().then(function() {
   *       // Once the user has logged in, sync the current URL
   *       // to the router:
   *       $urlRouter.sync();
   *     });
   *   });
   *
   *   // Configures $urlRouter's listener *after* your custom listener
   *   $urlRouter.listen();
   * });
   * </pre>
   *
   * @param {boolean} defer Indicates whether to defer location change interception. Passing
            no parameter is equivalent to `true`.
   */
  this.deferIntercept = function (defer) {
    if (defer === undefined) defer = true;
    interceptDeferred = defer;
  };

  /**
   * @ngdoc object
   * @name ui.router.router.$urlRouter
   *
   * @requires $location
   * @requires $rootScope
   * @requires $injector
   * @requires $browser
   *
   * @description
   *
   */
  this.$get = $get;
  $get.$inject = ['$location', '$rootScope', '$injector', '$browser', '$sniffer'];
  function $get(   $location,   $rootScope,   $injector,   $browser,   $sniffer) {

    var baseHref = $browser.baseHref(), location = $location.url(), lastPushedUrl;

    function appendBasePath(url, isHtml5, absolute) {
      if (baseHref === '/') return url;
      if (isHtml5) return baseHref.slice(0, -1) + url;
      if (absolute) return baseHref.slice(1) + url;
      return url;
    }

    // TODO: Optimize groups of rules with non-empty prefix into some sort of decision tree
    function update(evt) {
      if (evt && evt.defaultPrevented) return;
      var ignoreUpdate = lastPushedUrl && $location.url() === lastPushedUrl;
      lastPushedUrl = undefined;
      // TODO: Re-implement this in 1.0 for https://github.com/angular-ui/ui-router/issues/1573
      //if (ignoreUpdate) return true;

      function check(rule) {
        var handled = rule($injector, $location);

        if (!handled) return false;
        if (isString(handled)) $location.replace().url(handled);
        return true;
      }
      var n = rules.length, i;

      for (i = 0; i < n; i++) {
        if (check(rules[i])) return;
      }
      // always check otherwise last to allow dynamic updates to the set of rules
      if (otherwise) check(otherwise);
    }

    function listen() {
      listener = listener || $rootScope.$on('$locationChangeSuccess', update);
      return listener;
    }

    if (!interceptDeferred) listen();

    return {
      /**
       * @ngdoc function
       * @name ui.router.router.$urlRouter#sync
       * @methodOf ui.router.router.$urlRouter
       *
       * @description
       * Triggers an update; the same update that happens when the address bar url changes, aka `$locationChangeSuccess`.
       * This method is useful when you need to use `preventDefault()` on the `$locationChangeSuccess` event,
       * perform some custom logic (route protection, auth, config, redirection, etc) and then finally proceed
       * with the transition by calling `$urlRouter.sync()`.
       *
       * @example
       * <pre>
       * angular.module('app', ['ui.router'])
       *   .run(function($rootScope, $urlRouter) {
       *     $rootScope.$on('$locationChangeSuccess', function(evt) {
       *       // Halt state change from even starting
       *       evt.preventDefault();
       *       // Perform custom logic
       *       var meetsRequirement = ...
       *       // Continue with the update and state transition if logic allows
       *       if (meetsRequirement) $urlRouter.sync();
       *     });
       * });
       * </pre>
       */
      sync: function() {
        update();
      },

      listen: function() {
        return listen();
      },

      update: function(read) {
        if (read) {
          location = $location.url();
          return;
        }
        if ($location.url() === location) return;

        $location.url(location);
        $location.replace();
      },

      push: function(urlMatcher, params, options) {
         var url = urlMatcher.format(params || {});

        // Handle the special hash param, if needed
        if (url !== null && params && params['#']) {
            url += '#' + params['#'];
        }

        $location.url(url);
        lastPushedUrl = options && options.$$avoidResync ? $location.url() : undefined;
        if (options && options.replace) $location.replace();
      },

      /**
       * @ngdoc function
       * @name ui.router.router.$urlRouter#href
       * @methodOf ui.router.router.$urlRouter
       *
       * @description
       * A URL generation method that returns the compiled URL for a given
       * {@link ui.router.util.type:UrlMatcher `UrlMatcher`}, populated with the provided parameters.
       *
       * @example
       * <pre>
       * $bob = $urlRouter.href(new UrlMatcher("/about/:person"), {
       *   person: "bob"
       * });
       * // $bob == "/about/bob";
       * </pre>
       *
       * @param {UrlMatcher} urlMatcher The `UrlMatcher` object which is used as the template of the URL to generate.
       * @param {object=} params An object of parameter values to fill the matcher's required parameters.
       * @param {object=} options Options object. The options are:
       *
       * - **`absolute`** - {boolean=false},  If true will generate an absolute url, e.g. "http://www.example.com/fullurl".
       *
       * @returns {string} Returns the fully compiled URL, or `null` if `params` fail validation against `urlMatcher`
       */
      href: function(urlMatcher, params, options) {
        if (!urlMatcher.validates(params)) return null;

        var isHtml5 = $locationProvider.html5Mode();
        if (angular.isObject(isHtml5)) {
          isHtml5 = isHtml5.enabled;
        }

        isHtml5 = isHtml5 && $sniffer.history;
        
        var url = urlMatcher.format(params);
        options = options || {};

        if (!isHtml5 && url !== null) {
          url = "#" + $locationProvider.hashPrefix() + url;
        }

        // Handle special hash param, if needed
        if (url !== null && params && params['#']) {
          url += '#' + params['#'];
        }

        url = appendBasePath(url, isHtml5, options.absolute);

        if (!options.absolute || !url) {
          return url;
        }

        var slash = (!isHtml5 && url ? '/' : ''), port = $location.port();
        port = (port === 80 || port === 443 ? '' : ':' + port);

        return [$location.protocol(), '://', $location.host(), port, slash, url].join('');
      }
    };
  }
}

angular.module('ui.router.router').provider('$urlRouter', $UrlRouterProvider);

/**
 * @ngdoc object
 * @name ui.router.state.$stateProvider
 *
 * @requires ui.router.router.$urlRouterProvider
 * @requires ui.router.util.$urlMatcherFactoryProvider
 *
 * @description
 * The new `$stateProvider` works similar to Angular's v1 router, but it focuses purely
 * on state.
 *
 * A state corresponds to a "place" in the application in terms of the overall UI and
 * navigation. A state describes (via the controller / template / view properties) what
 * the UI looks like and does at that place.
 *
 * States often have things in common, and the primary way of factoring out these
 * commonalities in this model is via the state hierarchy, i.e. parent/child states aka
 * nested states.
 *
 * The `$stateProvider` provides interfaces to declare these states for your app.
 */
$StateProvider.$inject = ['$urlRouterProvider', '$urlMatcherFactoryProvider'];
function $StateProvider(   $urlRouterProvider,   $urlMatcherFactory) {

  var root, states = {}, $state, queue = {}, abstractKey = 'abstract';

  // Builds state properties from definition passed to registerState()
  var stateBuilder = {

    // Derive parent state from a hierarchical name only if 'parent' is not explicitly defined.
    // state.children = [];
    // if (parent) parent.children.push(state);
    parent: function(state) {
      if (isDefined(state.parent) && state.parent) return findState(state.parent);
      // regex matches any valid composite state name
      // would match "contact.list" but not "contacts"
      var compositeName = /^(.+)\.[^.]+$/.exec(state.name);
      return compositeName ? findState(compositeName[1]) : root;
    },

    // inherit 'data' from parent and override by own values (if any)
    data: function(state) {
      if (state.parent && state.parent.data) {
        state.data = state.self.data = inherit(state.parent.data, state.data);
      }
      return state.data;
    },

    // Build a URLMatcher if necessary, either via a relative or absolute URL
    url: function(state) {
      var url = state.url, config = { params: state.params || {} };

      if (isString(url)) {
        if (url.charAt(0) == '^') return $urlMatcherFactory.compile(url.substring(1), config);
        return (state.parent.navigable || root).url.concat(url, config);
      }

      if (!url || $urlMatcherFactory.isMatcher(url)) return url;
      throw new Error("Invalid url '" + url + "' in state '" + state + "'");
    },

    // Keep track of the closest ancestor state that has a URL (i.e. is navigable)
    navigable: function(state) {
      return state.url ? state : (state.parent ? state.parent.navigable : null);
    },

    // Own parameters for this state. state.url.params is already built at this point. Create and add non-url params
    ownParams: function(state) {
      var params = state.url && state.url.params || new $$UMFP.ParamSet();
      forEach(state.params || {}, function(config, id) {
        if (!params[id]) params[id] = new $$UMFP.Param(id, null, config, "config");
      });
      return params;
    },

    // Derive parameters for this state and ensure they're a super-set of parent's parameters
    params: function(state) {
      var ownParams = pick(state.ownParams, state.ownParams.$$keys());
      return state.parent && state.parent.params ? extend(state.parent.params.$$new(), ownParams) : new $$UMFP.ParamSet();
    },

    // If there is no explicit multi-view configuration, make one up so we don't have
    // to handle both cases in the view directive later. Note that having an explicit
    // 'views' property will mean the default unnamed view properties are ignored. This
    // is also a good time to resolve view names to absolute names, so everything is a
    // straight lookup at link time.
    views: function(state) {
      var views = {};

      forEach(isDefined(state.views) ? state.views : { '': state }, function (view, name) {
        if (name.indexOf('@') < 0) name += '@' + state.parent.name;
        views[name] = view;
      });
      return views;
    },

    // Keep a full path from the root down to this state as this is needed for state activation.
    path: function(state) {
      return state.parent ? state.parent.path.concat(state) : []; // exclude root from path
    },

    // Speed up $state.contains() as it's used a lot
    includes: function(state) {
      var includes = state.parent ? extend({}, state.parent.includes) : {};
      includes[state.name] = true;
      return includes;
    },

    $delegates: {}
  };

  function isRelative(stateName) {
    return stateName.indexOf(".") === 0 || stateName.indexOf("^") === 0;
  }

  function findState(stateOrName, base) {
    if (!stateOrName) return undefined;

    var isStr = isString(stateOrName),
        name  = isStr ? stateOrName : stateOrName.name,
        path  = isRelative(name);

    if (path) {
      if (!base) throw new Error("No reference point given for path '"  + name + "'");
      base = findState(base);
      
      var rel = name.split("."), i = 0, pathLength = rel.length, current = base;

      for (; i < pathLength; i++) {
        if (rel[i] === "" && i === 0) {
          current = base;
          continue;
        }
        if (rel[i] === "^") {
          if (!current.parent) throw new Error("Path '" + name + "' not valid for state '" + base.name + "'");
          current = current.parent;
          continue;
        }
        break;
      }
      rel = rel.slice(i).join(".");
      name = current.name + (current.name && rel ? "." : "") + rel;
    }
    var state = states[name];

    if (state && (isStr || (!isStr && (state === stateOrName || state.self === stateOrName)))) {
      return state;
    }
    return undefined;
  }

  function queueState(parentName, state) {
    if (!queue[parentName]) {
      queue[parentName] = [];
    }
    queue[parentName].push(state);
  }

  function flushQueuedChildren(parentName) {
    var queued = queue[parentName] || [];
    while(queued.length) {
      registerState(queued.shift());
    }
  }

  function registerState(state) {
    // Wrap a new object around the state so we can store our private details easily.
    state = inherit(state, {
      self: state,
      resolve: state.resolve || {},
      toString: function() { return this.name; }
    });

    var name = state.name;
    if (!isString(name) || name.indexOf('@') >= 0) throw new Error("State must have a valid name");
    if (states.hasOwnProperty(name)) throw new Error("State '" + name + "' is already defined");

    // Get parent name
    var parentName = (name.indexOf('.') !== -1) ? name.substring(0, name.lastIndexOf('.'))
        : (isString(state.parent)) ? state.parent
        : (isObject(state.parent) && isString(state.parent.name)) ? state.parent.name
        : '';

    // If parent is not registered yet, add state to queue and register later
    if (parentName && !states[parentName]) {
      return queueState(parentName, state.self);
    }

    for (var key in stateBuilder) {
      if (isFunction(stateBuilder[key])) state[key] = stateBuilder[key](state, stateBuilder.$delegates[key]);
    }
    states[name] = state;

    // Register the state in the global state list and with $urlRouter if necessary.
    if (!state[abstractKey] && state.url) {
      $urlRouterProvider.when(state.url, ['$match', '$stateParams', function ($match, $stateParams) {
        if ($state.$current.navigable != state || !equalForKeys($match, $stateParams)) {
          $state.transitionTo(state, $match, { inherit: true, location: false });
        }
      }]);
    }

    // Register any queued children
    flushQueuedChildren(name);

    return state;
  }

  // Checks text to see if it looks like a glob.
  function isGlob (text) {
    return text.indexOf('*') > -1;
  }

  // Returns true if glob matches current $state name.
  function doesStateMatchGlob (glob) {
    var globSegments = glob.split('.'),
        segments = $state.$current.name.split('.');

    //match single stars
    for (var i = 0, l = globSegments.length; i < l; i++) {
      if (globSegments[i] === '*') {
        segments[i] = '*';
      }
    }

    //match greedy starts
    if (globSegments[0] === '**') {
       segments = segments.slice(indexOf(segments, globSegments[1]));
       segments.unshift('**');
    }
    //match greedy ends
    if (globSegments[globSegments.length - 1] === '**') {
       segments.splice(indexOf(segments, globSegments[globSegments.length - 2]) + 1, Number.MAX_VALUE);
       segments.push('**');
    }

    if (globSegments.length != segments.length) {
      return false;
    }

    return segments.join('') === globSegments.join('');
  }


  // Implicit root state that is always active
  root = registerState({
    name: '',
    url: '^',
    views: null,
    'abstract': true
  });
  root.navigable = null;


  /**
   * @ngdoc function
   * @name ui.router.state.$stateProvider#decorator
   * @methodOf ui.router.state.$stateProvider
   *
   * @description
   * Allows you to extend (carefully) or override (at your own peril) the 
   * `stateBuilder` object used internally by `$stateProvider`. This can be used 
   * to add custom functionality to ui-router, for example inferring templateUrl 
   * based on the state name.
   *
   * When passing only a name, it returns the current (original or decorated) builder
   * function that matches `name`.
   *
   * The builder functions that can be decorated are listed below. Though not all
   * necessarily have a good use case for decoration, that is up to you to decide.
   *
   * In addition, users can attach custom decorators, which will generate new 
   * properties within the state's internal definition. There is currently no clear 
   * use-case for this beyond accessing internal states (i.e. $state.$current), 
   * however, expect this to become increasingly relevant as we introduce additional 
   * meta-programming features.
   *
   * **Warning**: Decorators should not be interdependent because the order of 
   * execution of the builder functions in non-deterministic. Builder functions 
   * should only be dependent on the state definition object and super function.
   *
   *
   * Existing builder functions and current return values:
   *
   * - **parent** `{object}` - returns the parent state object.
   * - **data** `{object}` - returns state data, including any inherited data that is not
   *   overridden by own values (if any).
   * - **url** `{object}` - returns a {@link ui.router.util.type:UrlMatcher UrlMatcher}
   *   or `null`.
   * - **navigable** `{object}` - returns closest ancestor state that has a URL (aka is 
   *   navigable).
   * - **params** `{object}` - returns an array of state params that are ensured to 
   *   be a super-set of parent's params.
   * - **views** `{object}` - returns a views object where each key is an absolute view 
   *   name (i.e. "viewName@stateName") and each value is the config object 
   *   (template, controller) for the view. Even when you don't use the views object 
   *   explicitly on a state config, one is still created for you internally.
   *   So by decorating this builder function you have access to decorating template 
   *   and controller properties.
   * - **ownParams** `{object}` - returns an array of params that belong to the state, 
   *   not including any params defined by ancestor states.
   * - **path** `{string}` - returns the full path from the root down to this state. 
   *   Needed for state activation.
   * - **includes** `{object}` - returns an object that includes every state that 
   *   would pass a `$state.includes()` test.
   *
   * @example
   * <pre>
   * // Override the internal 'views' builder with a function that takes the state
   * // definition, and a reference to the internal function being overridden:
   * $stateProvider.decorator('views', function (state, parent) {
   *   var result = {},
   *       views = parent(state);
   *
   *   angular.forEach(views, function (config, name) {
   *     var autoName = (state.name + '.' + name).replace('.', '/');
   *     config.templateUrl = config.templateUrl || '/partials/' + autoName + '.html';
   *     result[name] = config;
   *   });
   *   return result;
   * });
   *
   * $stateProvider.state('home', {
   *   views: {
   *     'contact.list': { controller: 'ListController' },
   *     'contact.item': { controller: 'ItemController' }
   *   }
   * });
   *
   * // ...
   *
   * $state.go('home');
   * // Auto-populates list and item views with /partials/home/contact/list.html,
   * // and /partials/home/contact/item.html, respectively.
   * </pre>
   *
   * @param {string} name The name of the builder function to decorate. 
   * @param {object} func A function that is responsible for decorating the original 
   * builder function. The function receives two parameters:
   *
   *   - `{object}` - state - The state config object.
   *   - `{object}` - super - The original builder function.
   *
   * @return {object} $stateProvider - $stateProvider instance
   */
  this.decorator = decorator;
  function decorator(name, func) {
    /*jshint validthis: true */
    if (isString(name) && !isDefined(func)) {
      return stateBuilder[name];
    }
    if (!isFunction(func) || !isString(name)) {
      return this;
    }
    if (stateBuilder[name] && !stateBuilder.$delegates[name]) {
      stateBuilder.$delegates[name] = stateBuilder[name];
    }
    stateBuilder[name] = func;
    return this;
  }

  /**
   * @ngdoc function
   * @name ui.router.state.$stateProvider#state
   * @methodOf ui.router.state.$stateProvider
   *
   * @description
   * Registers a state configuration under a given state name. The stateConfig object
   * has the following acceptable properties.
   *
   * @param {string} name A unique state name, e.g. "home", "about", "contacts".
   * To create a parent/child state use a dot, e.g. "about.sales", "home.newest".
   * @param {object} stateConfig State configuration object.
   * @param {string|function=} stateConfig.template
   * <a id='template'></a>
   *   html template as a string or a function that returns
   *   an html template as a string which should be used by the uiView directives. This property 
   *   takes precedence over templateUrl.
   *   
   *   If `template` is a function, it will be called with the following parameters:
   *
   *   - {array.&lt;object&gt;} - state parameters extracted from the current $location.path() by
   *     applying the current state
   *
   * <pre>template:
   *   "<h1>inline template definition</h1>" +
   *   "<div ui-view></div>"</pre>
   * <pre>template: function(params) {
   *       return "<h1>generated template</h1>"; }</pre>
   * </div>
   *
   * @param {string|function=} stateConfig.templateUrl
   * <a id='templateUrl'></a>
   *
   *   path or function that returns a path to an html
   *   template that should be used by uiView.
   *   
   *   If `templateUrl` is a function, it will be called with the following parameters:
   *
   *   - {array.&lt;object&gt;} - state parameters extracted from the current $location.path() by 
   *     applying the current state
   *
   * <pre>templateUrl: "home.html"</pre>
   * <pre>templateUrl: function(params) {
   *     return myTemplates[params.pageId]; }</pre>
   *
   * @param {function=} stateConfig.templateProvider
   * <a id='templateProvider'></a>
   *    Provider function that returns HTML content string.
   * <pre> templateProvider:
   *       function(MyTemplateService, params) {
   *         return MyTemplateService.getTemplate(params.pageId);
   *       }</pre>
   *
   * @param {string|function=} stateConfig.controller
   * <a id='controller'></a>
   *
   *  Controller fn that should be associated with newly
   *   related scope or the name of a registered controller if passed as a string.
   *   Optionally, the ControllerAs may be declared here.
   * <pre>controller: "MyRegisteredController"</pre>
   * <pre>controller:
   *     "MyRegisteredController as fooCtrl"}</pre>
   * <pre>controller: function($scope, MyService) {
   *     $scope.data = MyService.getData(); }</pre>
   *
   * @param {function=} stateConfig.controllerProvider
   * <a id='controllerProvider'></a>
   *
   * Injectable provider function that returns the actual controller or string.
   * <pre>controllerProvider:
   *   function(MyResolveData) {
   *     if (MyResolveData.foo)
   *       return "FooCtrl"
   *     else if (MyResolveData.bar)
   *       return "BarCtrl";
   *     else return function($scope) {
   *       $scope.baz = "Qux";
   *     }
   *   }</pre>
   *
   * @param {string=} stateConfig.controllerAs
   * <a id='controllerAs'></a>
   * 
   * A controller alias name. If present the controller will be
   *   published to scope under the controllerAs name.
   * <pre>controllerAs: "myCtrl"</pre>
   *
   * @param {string|object=} stateConfig.parent
   * <a id='parent'></a>
   * Optionally specifies the parent state of this state.
   *
   * <pre>parent: 'parentState'</pre>
   * <pre>parent: parentState // JS variable</pre>
   *
   * @param {object=} stateConfig.resolve
   * <a id='resolve'></a>
   *
   * An optional map&lt;string, function&gt; 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 before the controller is instantiated.
   *   If all the promises are resolved successfully, the $stateChangeSuccess event is fired
   *   and the values of the resolved promises are injected into any controllers that reference them.
   *   If any  of the promises are rejected the $stateChangeError event is fired.
   *
   *   The map object is:
   *   
   *   - key - {string}: name of dependency to be injected into controller
   *   - factory - {string|function}: If string then it is alias for service. Otherwise if function, 
   *     it is injected and return value it treated as dependency. If result is a promise, it is 
   *     resolved before its value is injected into controller.
   *
   * <pre>resolve: {
   *     myResolve1:
   *       function($http, $stateParams) {
   *         return $http.get("/api/foos/"+stateParams.fooID);
   *       }
   *     }</pre>
   *
   * @param {string=} stateConfig.url
   * <a id='url'></a>
   *
   *   A url fragment with optional parameters. When a state is navigated or
   *   transitioned to, the `$stateParams` service will be populated with any 
   *   parameters that were passed.
   *
   *   (See {@link ui.router.util.type:UrlMatcher UrlMatcher} `UrlMatcher`} for
   *   more details on acceptable patterns )
   *
   * examples:
   * <pre>url: "/home"
   * url: "/users/:userid"
   * url: "/books/{bookid:[a-zA-Z_-]}"
   * url: "/books/{categoryid:int}"
   * url: "/books/{publishername:string}/{categoryid:int}"
   * url: "/messages?before&after"
   * url: "/messages?{before:date}&{after:date}"
   * url: "/messages/:mailboxid?{before:date}&{after:date}"
   * </pre>
   *
   * @param {object=} stateConfig.views
   * <a id='views'></a>
   * an optional map&lt;string, object&gt; which defined multiple views, or targets views
   * manually/explicitly.
   *
   * Examples:
   *
   * Targets three named `ui-view`s in the parent state's template
   * <pre>views: {
   *     header: {
   *       controller: "headerCtrl",
   *       templateUrl: "header.html"
   *     }, body: {
   *       controller: "bodyCtrl",
   *       templateUrl: "body.html"
   *     }, footer: {
   *       controller: "footCtrl",
   *       templateUrl: "footer.html"
   *     }
   *   }</pre>
   *
   * Targets named `ui-view="header"` from grandparent state 'top''s template, and named `ui-view="body" from parent state's template.
   * <pre>views: {
   *     'header@top': {
   *       controller: "msgHeaderCtrl",
   *       templateUrl: "msgHeader.html"
   *     }, 'body': {
   *       controller: "messagesCtrl",
   *       templateUrl: "messages.html"
   *     }
   *   }</pre>
   *
   * @param {boolean=} [stateConfig.abstract=false]
   * <a id='abstract'></a>
   * An abstract state will never be directly activated,
   *   but can provide inherited properties to its common children states.
   * <pre>abstract: true</pre>
   *
   * @param {function=} stateConfig.onEnter
   * <a id='onEnter'></a>
   *
   * Callback function for when a state is entered. Good way
   *   to trigger an action or dispatch an event, such as opening a dialog.
   * If minifying your scripts, make sure to explicitly annotate this function,
   * because it won't be automatically annotated by your build tools.
   *
   * <pre>onEnter: function(MyService, $stateParams) {
   *     MyService.foo($stateParams.myParam);
   * }</pre>
   *
   * @param {function=} stateConfig.onExit
   * <a id='onExit'></a>
   *
   * Callback function for when a state is exited. Good way to
   *   trigger an action or dispatch an event, such as opening a dialog.
   * If minifying your scripts, make sure to explicitly annotate this function,
   * because it won't be automatically annotated by your build tools.
   *
   * <pre>onExit: function(MyService, $stateParams) {
   *     MyService.cleanup($stateParams.myParam);
   * }</pre>
   *
   * @param {boolean=} [stateConfig.reloadOnSearch=true]
   * <a id='reloadOnSearch'></a>
   *
   * If `false`, will not retrigger the same state
   *   just because a search/query parameter has changed (via $location.search() or $location.hash()). 
   *   Useful for when you'd like to modify $location.search() without triggering a reload.
   * <pre>reloadOnSearch: false</pre>
   *
   * @param {object=} stateConfig.data
   * <a id='data'></a>
   *
   * Arbitrary data object, useful for custom configuration.  The parent state's `data` is
   *   prototypally inherited.  In other words, adding a data property to a state adds it to
   *   the entire subtree via prototypal inheritance.
   *
   * <pre>data: {
   *     requiredRole: 'foo'
   * } </pre>
   *
   * @param {object=} stateConfig.params
   * <a id='params'></a>
   *
   * A map which optionally configures parameters declared in the `url`, or
   *   defines additional non-url parameters.  For each parameter being
   *   configured, add a configuration object keyed to the name of the parameter.
   *
   *   Each parameter configuration object may contain the following properties:
   *
   *   - ** value ** - {object|function=}: specifies the default value for this
   *     parameter.  This implicitly sets this parameter as optional.
   *
   *     When UI-Router routes to a state and no value is
   *     specified for this parameter in the URL or transition, the
   *     default value will be used instead.  If `value` is a function,
   *     it will be injected and invoked, and the return value used.
   *
   *     *Note*: `undefined` is treated as "no default value" while `null`
   *     is treated as "the default value is `null`".
   *
   *     *Shorthand*: If you only need to configure the default value of the
   *     parameter, you may use a shorthand syntax.   In the **`params`**
   *     map, instead mapping the param name to a full parameter configuration
   *     object, simply set map it to the default parameter value, e.g.:
   *
   * <pre>// define a parameter's default value
   * params: {
   *     param1: { value: "defaultValue" }
   * }
   * // shorthand default values
   * params: {
   *     param1: "defaultValue",
   *     param2: "param2Default"
   * }</pre>
   *
   *   - ** array ** - {boolean=}: *(default: false)* If true, the param value will be
   *     treated as an array of values.  If you specified a Type, the value will be
   *     treated as an array of the specified Type.  Note: query parameter values
   *     default to a special `"auto"` mode.
   *
   *     For query parameters in `"auto"` mode, if multiple  values for a single parameter
   *     are present in the URL (e.g.: `/foo?bar=1&bar=2&bar=3`) then the values
   *     are mapped to an array (e.g.: `{ foo: [ '1', '2', '3' ] }`).  However, if
   *     only one value is present (e.g.: `/foo?bar=1`) then the value is treated as single
   *     value (e.g.: `{ foo: '1' }`).
   *
   * <pre>params: {
   *     param1: { array: true }
   * }</pre>
   *
   *   - ** squash ** - {bool|string=}: `squash` configures how a default parameter value is represented in the URL when
   *     the current parameter value is the same as the default value. If `squash` is not set, it uses the
   *     configured default squash policy.
   *     (See {@link ui.router.util.$urlMatcherFactory#methods_defaultSquashPolicy `defaultSquashPolicy()`})
   *
   *   There are three squash settings:
   *
   *     - false: The parameter's default value is not squashed.  It is encoded and included in the URL
   *     - true: The parameter's default value is omitted from the URL.  If the parameter is preceeded and followed
   *       by slashes in the state's `url` declaration, then one of those slashes are omitted.
   *       This can allow for cleaner looking URLs.
   *     - `"<arbitrary string>"`: The parameter's default value is replaced with an arbitrary placeholder of  your choice.
   *
   * <pre>params: {
   *     param1: {
   *       value: "defaultId",
   *       squash: true
   * } }
   * // squash "defaultValue" to "~"
   * params: {
   *     param1: {
   *       value: "defaultValue",
   *       squash: "~"
   * } }
   * </pre>
   *
   *
   * @example
   * <pre>
   * // Some state name examples
   *
   * // stateName can be a single top-level name (must be unique).
   * $stateProvider.state("home", {});
   *
   * // Or it can be a nested state name. This state is a child of the
   * // above "home" state.
   * $stateProvider.state("home.newest", {});
   *
   * // Nest states as deeply as needed.
   * $stateProvider.state("home.newest.abc.xyz.inception", {});
   *
   * // state() returns $stateProvider, so you can chain state declarations.
   * $stateProvider
   *   .state("home", {})
   *   .state("about", {})
   *   .state("contacts", {});
   * </pre>
   *
   */
  this.state = state;
  function state(name, definition) {
    /*jshint validthis: true */
    if (isObject(name)) definition = name;
    else definition.name = name;
    registerState(definition);
    return this;
  }

  /**
   * @ngdoc object
   * @name ui.router.state.$state
   *
   * @requires $rootScope
   * @requires $q
   * @requires ui.router.state.$view
   * @requires $injector
   * @requires ui.router.util.$resolve
   * @requires ui.router.state.$stateParams
   * @requires ui.router.router.$urlRouter
   *
   * @property {object} params A param object, e.g. {sectionId: section.id)}, that 
   * you'd like to test against the current active state.
   * @property {object} current A reference to the state's config object. However 
   * you passed it in. Useful for accessing custom data.
   * @property {object} transition Currently pending transition. A promise that'll 
   * resolve or reject.
   *
   * @description
   * `$state` service is responsible for representing states as well as transitioning
   * between them. It also provides interfaces to ask for current state or even states
   * you're coming from.
   */
  this.$get = $get;
  $get.$inject = ['$rootScope', '$q', '$view', '$injector', '$resolve', '$stateParams', '$urlRouter', '$location', '$urlMatcherFactory'];
  function $get(   $rootScope,   $q,   $view,   $injector,   $resolve,   $stateParams,   $urlRouter,   $location,   $urlMatcherFactory) {

    var TransitionSuperseded = $q.reject(new Error('transition superseded'));
    var TransitionPrevented = $q.reject(new Error('transition prevented'));
    var TransitionAborted = $q.reject(new Error('transition aborted'));
    var TransitionFailed = $q.reject(new Error('transition failed'));

    // Handles the case where a state which is the target of a transition is not found, and the user
    // can optionally retry or defer the transition
    function handleRedirect(redirect, state, params, options) {
      /**
       * @ngdoc event
       * @name ui.router.state.$state#$stateNotFound
       * @eventOf ui.router.state.$state
       * @eventType broadcast on root scope
       * @description
       * Fired when a requested state **cannot be found** using the provided state name during transition.
       * The event is broadcast allowing any handlers a single chance to deal with the error (usually by
       * lazy-loading the unfound state). A special `unfoundState` object is passed to the listener handler,
       * you can see its three properties in the example. You can use `event.preventDefault()` to abort the
       * transition and the promise returned from `go` will be rejected with a `'transition aborted'` value.
       *
       * @param {Object} event Event object.
       * @param {Object} unfoundState Unfound State information. Contains: `to, toParams, options` properties.
       * @param {State} fromState Current state object.
       * @param {Object} fromParams Current state params.
       *
       * @example
       *
       * <pre>
       * // somewhere, assume lazy.state has not been defined
       * $state.go("lazy.state", {a:1, b:2}, {inherit:false});
       *
       * // somewhere else
       * $scope.$on('$stateNotFound',
       * function(event, unfoundState, fromState, fromParams){
       *     console.log(unfoundState.to); // "lazy.state"
       *     console.log(unfoundState.toParams); // {a:1, b:2}
       *     console.log(unfoundState.options); // {inherit:false} + default options
       * })
       * </pre>
       */
      var evt = $rootScope.$broadcast('$stateNotFound', redirect, state, params);

      if (evt.defaultPrevented) {
        $urlRouter.update();
        return TransitionAborted;
      }

      if (!evt.retry) {
        return null;
      }

      // Allow the handler to return a promise to defer state lookup retry
      if (options.$retry) {
        $urlRouter.update();
        return TransitionFailed;
      }
      var retryTransition = $state.transition = $q.when(evt.retry);

      retryTransition.then(function() {
        if (retryTransition !== $state.transition) return TransitionSuperseded;
        redirect.options.$retry = true;
        return $state.transitionTo(redirect.to, redirect.toParams, redirect.options);
      }, function() {
        return TransitionAborted;
      });
      $urlRouter.update();

      return retryTransition;
    }

    root.locals = { resolve: null, globals: { $stateParams: {} } };

    $state = {
      params: {},
      current: root.self,
      $current: root,
      transition: null
    };

    /**
     * @ngdoc function
     * @name ui.router.state.$state#reload
     * @methodOf ui.router.state.$state
     *
     * @description
     * A method that force reloads the current state. All resolves are re-resolved,
     * controllers reinstantiated, and events re-fired.
     *
     * @example
     * <pre>
     * var app angular.module('app', ['ui.router']);
     *
     * app.controller('ctrl', function ($scope, $state) {
     *   $scope.reload = function(){
     *     $state.reload();
     *   }
     * });
     * </pre>
     *
     * `reload()` is just an alias for:
     * <pre>
     * $state.transitionTo($state.current, $stateParams, { 
     *   reload: true, inherit: false, notify: true
     * });
     * </pre>
     *
     * @param {string=|object=} state - A state name or a state object, which is the root of the resolves to be re-resolved.
     * @example
     * <pre>
     * //assuming app application consists of 3 states: 'contacts', 'contacts.detail', 'contacts.detail.item' 
     * //and current state is 'contacts.detail.item'
     * var app angular.module('app', ['ui.router']);
     *
     * app.controller('ctrl', function ($scope, $state) {
     *   $scope.reload = function(){
     *     //will reload 'contact.detail' and 'contact.detail.item' states
     *     $state.reload('contact.detail');
     *   }
     * });
     * </pre>
     *
     * `reload()` is just an alias for:
     * <pre>
     * $state.transitionTo($state.current, $stateParams, { 
     *   reload: true, inherit: false, notify: true
     * });
     * </pre>

     * @returns {promise} A promise representing the state of the new transition. See
     * {@link ui.router.state.$state#methods_go $state.go}.
     */
    $state.reload = function reload(state) {
      return $state.transitionTo($state.current, $stateParams, { reload: state || true, inherit: false, notify: true});
    };

    /**
     * @ngdoc function
     * @name ui.router.state.$state#go
     * @methodOf ui.router.state.$state
     *
     * @description
     * Convenience method for transitioning to a new state. `$state.go` calls 
     * `$state.transitionTo` internally but automatically sets options to 
     * `{ location: true, inherit: true, relative: $state.$current, notify: true }`. 
     * This allows you to easily use an absolute or relative to path and specify 
     * only the parameters you'd like to update (while letting unspecified parameters 
     * inherit from the currently active ancestor states).
     *
     * @example
     * <pre>
     * var app = angular.module('app', ['ui.router']);
     *
     * app.controller('ctrl', function ($scope, $state) {
     *   $scope.changeState = function () {
     *     $state.go('contact.detail');
     *   };
     * });
     * </pre>
     * <img src='../ngdoc_assets/StateGoExamples.png'/>
     *
     * @param {string} to Absolute state name or relative state path. Some examples:
     *
     * - `$state.go('contact.detail')` - will go to the `contact.detail` state
     * - `$state.go('^')` - will go to a parent state
     * - `$state.go('^.sibling')` - will go to a sibling state
     * - `$state.go('.child.grandchild')` - will go to grandchild state
     *
     * @param {object=} params A map of the parameters that will be sent to the state, 
     * will populate $stateParams. Any parameters that are not specified will be inherited from currently 
     * defined parameters. Only parameters specified in the state definition can be overridden, new 
     * parameters will be ignored. This allows, for example, going to a sibling state that shares parameters
     * specified in a parent state. Parameter inheritance only works between common ancestor states, I.e.
     * transitioning to a sibling will get you the parameters for all parents, transitioning to a child
     * will get you all current parameters, etc.
     * @param {object=} options Options object. The options are:
     *
     * - **`location`** - {boolean=true|string=} - If `true` will update the url in the location bar, if `false`
     *    will not. If string, must be `"replace"`, which will update url and also replace last history record.
     * - **`inherit`** - {boolean=true}, If `true` will inherit url parameters from current url.
     * - **`relative`** - {object=$state.$current}, When transitioning with relative path (e.g '^'), 
     *    defines which state to be relative from.
     * - **`notify`** - {boolean=true}, If `true` will broadcast $stateChangeStart and $stateChangeSuccess events.
     * - **`reload`** (v0.2.5) - {boolean=false|string|object}, If `true` will force transition even if no state or params
     *    have changed.  It will reload the resolves and views of the current state and parent states.
     *    If `reload` is a string (or state object), the state object is fetched (by name, or object reference); and \
     *    the transition reloads the resolves and views for that matched state, and all its children states.
     *
     * @returns {promise} A promise representing the state of the new transition.
     *
     * Possible success values:
     *
     * - $state.current
     *
     * <br/>Possible rejection values:
     *
     * - 'transition superseded' - when a newer transition has been started after this one
     * - 'transition prevented' - when `event.preventDefault()` has been called in a `$stateChangeStart` listener
     * - 'transition aborted' - when `event.preventDefault()` has been called in a `$stateNotFound` listener or
     *   when a `$stateNotFound` `event.retry` promise errors.
     * - 'transition failed' - when a state has been unsuccessfully found after 2 tries.
     * - *resolve error* - when an error has occurred with a `resolve`
     *
     */
    $state.go = function go(to, params, options) {
      return $state.transitionTo(to, params, extend({ inherit: true, relative: $state.$current }, options));
    };

    /**
     * @ngdoc function
     * @name ui.router.state.$state#transitionTo
     * @methodOf ui.router.state.$state
     *
     * @description
     * Low-level method for transitioning to a new state. {@link ui.router.state.$state#methods_go $state.go}
     * uses `transitionTo` internally. `$state.go` is recommended in most situations.
     *
     * @example
     * <pre>
     * var app = angular.module('app', ['ui.router']);
     *
     * app.controller('ctrl', function ($scope, $state) {
     *   $scope.changeState = function () {
     *     $state.transitionTo('contact.detail');
     *   };
     * });
     * </pre>
     *
     * @param {string} to State name.
     * @param {object=} toParams A map of the parameters that will be sent to the state,
     * will populate $stateParams.
     * @param {object=} options Options object. The options are:
     *
     * - **`location`** - {boolean=true|string=} - If `true` will update the url in the location bar, if `false`
     *    will not. If string, must be `"replace"`, which will update url and also replace last history record.
     * - **`inherit`** - {boolean=false}, If `true` will inherit url parameters from current url.
     * - **`relative`** - {object=}, When transitioning with relative path (e.g '^'), 
     *    defines which state to be relative from.
     * - **`notify`** - {boolean=true}, If `true` will broadcast $stateChangeStart and $stateChangeSuccess events.
     * - **`reload`** (v0.2.5) - {boolean=false|string=|object=}, If `true` will force transition even if the state or params 
     *    have not changed, aka a reload of the same state. It differs from reloadOnSearch because you'd
     *    use this when you want to force a reload when *everything* is the same, including search params.
     *    if String, then will reload the state with the name given in reload, and any children.
     *    if Object, then a stateObj is expected, will reload the state found in stateObj, and any children.
     *
     * @returns {promise} A promise representing the state of the new transition. See
     * {@link ui.router.state.$state#methods_go $state.go}.
     */
    $state.transitionTo = function transitionTo(to, toParams, options) {
      toParams = toParams || {};
      options = extend({
        location: true, inherit: false, relative: null, notify: true, reload: false, $retry: false
      }, options || {});

      var from = $state.$current, fromParams = $state.params, fromPath = from.path;
      var evt, toState = findState(to, options.relative);

      // Store the hash param for later (since it will be stripped out by various methods)
      var hash = toParams['#'];

      if (!isDefined(toState)) {
        var redirect = { to: to, toParams: toParams, options: options };
        var redirectResult = handleRedirect(redirect, from.self, fromParams, options);

        if (redirectResult) {
          return redirectResult;
        }

        // Always retry once if the $stateNotFound was not prevented
        // (handles either redirect changed or state lazy-definition)
        to = redirect.to;
        toParams = redirect.toParams;
        options = redirect.options;
        toState = findState(to, options.relative);

        if (!isDefined(toState)) {
          if (!options.relative) throw new Error("No such state '" + to + "'");
          throw new Error("Could not resolve '" + to + "' from state '" + options.relative + "'");
        }
      }
      if (toState[abstractKey]) throw new Error("Cannot transition to abstract state '" + to + "'");
      if (options.inherit) toParams = inheritParams($stateParams, toParams || {}, $state.$current, toState);
      if (!toState.params.$$validates(toParams)) return TransitionFailed;

      toParams = toState.params.$$values(toParams);
      to = toState;

      var toPath = to.path;

      // Starting from the root of the path, keep all levels that haven't changed
      var keep = 0, state = toPath[keep], locals = root.locals, toLocals = [];

      if (!options.reload) {
        while (state && state === fromPath[keep] && state.ownParams.$$equals(toParams, fromParams)) {
          locals = toLocals[keep] = state.locals;
          keep++;
          state = toPath[keep];
        }
      } else if (isString(options.reload) || isObject(options.reload)) {
        if (isObject(options.reload) && !options.reload.name) {
          throw new Error('Invalid reload state object');
        }
        
        var reloadState = options.reload === true ? fromPath[0] : findState(options.reload);
        if (options.reload && !reloadState) {
          throw new Error("No such reload state '" + (isString(options.reload) ? options.reload : options.reload.name) + "'");
        }

        while (state && state === fromPath[keep] && state !== reloadState) {
          locals = toLocals[keep] = state.locals;
          keep++;
          state = toPath[keep];
        }
      }

      // If we're going to the same state and all locals are kept, we've got nothing to do.
      // But clear 'transition', as we still want to cancel any other pending transitions.
      // TODO: We may not want to bump 'transition' if we're called from a location change
      // that we've initiated ourselves, because we might accidentally abort a legitimate
      // transition initiated from code?
      if (shouldSkipReload(to, toParams, from, fromParams, locals, options)) {
        if (hash) toParams['#'] = hash;
        $state.params = toParams;
        copy($state.params, $stateParams);
        copy(filterByKeys(to.params.$$keys(), $stateParams), to.locals.globals.$stateParams);
        if (options.location && to.navigable && to.navigable.url) {
          $urlRouter.push(to.navigable.url, toParams, {
            $$avoidResync: true, replace: options.location === 'replace'
          });
          $urlRouter.update(true);
        }
        $state.transition = null;
        return $q.when($state.current);
      }

      // Filter parameters before we pass them to event handlers etc.
      toParams = filterByKeys(to.params.$$keys(), toParams || {});
      
      // Re-add the saved hash before we start returning things or broadcasting $stateChangeStart
      if (hash) toParams['#'] = hash;
      
      // Broadcast start event and cancel the transition if requested
      if (options.notify) {
        /**
         * @ngdoc event
         * @name ui.router.state.$state#$stateChangeStart
         * @eventOf ui.router.state.$state
         * @eventType broadcast on root scope
         * @description
         * Fired when the state transition **begins**. You can use `event.preventDefault()`
         * to prevent the transition from happening and then the transition promise will be
         * rejected with a `'transition prevented'` value.
         *
         * @param {Object} event Event object.
         * @param {State} toState The state being transitioned to.
         * @param {Object} toParams The params supplied to the `toState`.
         * @param {State} fromState The current state, pre-transition.
         * @param {Object} fromParams The params supplied to the `fromState`.
         *
         * @example
         *
         * <pre>
         * $rootScope.$on('$stateChangeStart',
         * function(event, toState, toParams, fromState, fromParams){
         *     event.preventDefault();
         *     // transitionTo() promise will be rejected with
         *     // a 'transition prevented' error
         * })
         * </pre>
         */
        if ($rootScope.$broadcast('$stateChangeStart', to.self, toParams, from.self, fromParams, options).defaultPrevented) {
          $rootScope.$broadcast('$stateChangeCancel', to.self, toParams, from.self, fromParams);
          //Don't update and resync url if there's been a new transition started. see issue #2238, #600
          if ($state.transition == null) $urlRouter.update();
          return TransitionPrevented;
        }
      }

      // Resolve locals for the remaining states, but don't update any global state just
      // yet -- if anything fails to resolve the current state needs to remain untouched.
      // We also set up an inheritance chain for the locals here. This allows the view directive
      // to quickly look up the correct definition for each view in the current state. Even
      // though we create the locals object itself outside resolveState(), it is initially
      // empty and gets filled asynchronously. We need to keep track of the promise for the
      // (fully resolved) current locals, and pass this down the chain.
      var resolved = $q.when(locals);

      for (var l = keep; l < toPath.length; l++, state = toPath[l]) {
        locals = toLocals[l] = inherit(locals);
        resolved = resolveState(state, toParams, state === to, resolved, locals, options);
      }

      // Once everything is resolved, we are ready to perform the actual transition
      // and return a promise for the new state. We also keep track of what the
      // current promise is, so that we can detect overlapping transitions and
      // keep only the outcome of the last transition.
      var transition = $state.transition = resolved.then(function () {
        var l, entering, exiting;

        if ($state.transition !== transition) return TransitionSuperseded;

        // Exit 'from' states not kept
        for (l = fromPath.length - 1; l >= keep; l--) {
          exiting = fromPath[l];
          if (exiting.self.onExit) {
            $injector.invoke(exiting.self.onExit, exiting.self, exiting.locals.globals);
          }
          exiting.locals = null;
        }

        // Enter 'to' states not kept
        for (l = keep; l < toPath.length; l++) {
          entering = toPath[l];
          entering.locals = toLocals[l];
          if (entering.self.onEnter) {
            $injector.invoke(entering.self.onEnter, entering.self, entering.locals.globals);
          }
        }

        // Run it again, to catch any transitions in callbacks
        if ($state.transition !== transition) return TransitionSuperseded;

        // Update globals in $state
        $state.$current = to;
        $state.current = to.self;
        $state.params = toParams;
        copy($state.params, $stateParams);
        $state.transition = null;

        if (options.location && to.navigable) {
          $urlRouter.push(to.navigable.url, to.navigable.locals.globals.$stateParams, {
            $$avoidResync: true, replace: options.location === 'replace'
          });
        }

        if (options.notify) {
        /**
         * @ngdoc event
         * @name ui.router.state.$state#$stateChangeSuccess
         * @eventOf ui.router.state.$state
         * @eventType broadcast on root scope
         * @description
         * Fired once the state transition is **complete**.
         *
         * @param {Object} event Event object.
         * @param {State} toState The state being transitioned to.
         * @param {Object} toParams The params supplied to the `toState`.
         * @param {State} fromState The current state, pre-transition.
         * @param {Object} fromParams The params supplied to the `fromState`.
         */
          $rootScope.$broadcast('$stateChangeSuccess', to.self, toParams, from.self, fromParams);
        }
        $urlRouter.update(true);

        return $state.current;
      }, function (error) {
        if ($state.transition !== transition) return TransitionSuperseded;

        $state.transition = null;
        /**
         * @ngdoc event
         * @name ui.router.state.$state#$stateChangeError
         * @eventOf ui.router.state.$state
         * @eventType broadcast on root scope
         * @description
         * Fired when an **error occurs** during transition. It's important to note that if you
         * have any errors in your resolve functions (javascript errors, non-existent services, etc)
         * they will not throw traditionally. You must listen for this $stateChangeError event to
         * catch **ALL** errors.
         *
         * @param {Object} event Event object.
         * @param {State} toState The state being transitioned to.
         * @param {Object} toParams The params supplied to the `toState`.
         * @param {State} fromState The current state, pre-transition.
         * @param {Object} fromParams The params supplied to the `fromState`.
         * @param {Error} error The resolve error object.
         */
        evt = $rootScope.$broadcast('$stateChangeError', to.self, toParams, from.self, fromParams, error);

        if (!evt.defaultPrevented) {
            $urlRouter.update();
        }

        return $q.reject(error);
      });

      return transition;
    };

    /**
     * @ngdoc function
     * @name ui.router.state.$state#is
     * @methodOf ui.router.state.$state
     *
     * @description
     * Similar to {@link ui.router.state.$state#methods_includes $state.includes},
     * but only checks for the full state name. If params is supplied then it will be
     * tested for strict equality against the current active params object, so all params
     * must match with none missing and no extras.
     *
     * @example
     * <pre>
     * $state.$current.name = 'contacts.details.item';
     *
     * // absolute name
     * $state.is('contact.details.item'); // returns true
     * $state.is(contactDetailItemStateObject); // returns true
     *
     * // relative name (. and ^), typically from a template
     * // E.g. from the 'contacts.details' template
     * <div ng-class="{highlighted: $state.is('.item')}">Item</div>
     * </pre>
     *
     * @param {string|object} stateOrName The state name (absolute or relative) or state object you'd like to check.
     * @param {object=} params A param object, e.g. `{sectionId: section.id}`, that you'd like
     * to test against the current active state.
     * @param {object=} options An options object.  The options are:
     *
     * - **`relative`** - {string|object} -  If `stateOrName` is a relative state name and `options.relative` is set, .is will
     * test relative to `options.relative` state (or name).
     *
     * @returns {boolean} Returns true if it is the state.
     */
    $state.is = function is(stateOrName, params, options) {
      options = extend({ relative: $state.$current }, options || {});
      var state = findState(stateOrName, options.relative);

      if (!isDefined(state)) { return undefined; }
      if ($state.$current !== state) { return false; }
      return params ? equalForKeys(state.params.$$values(params), $stateParams) : true;
    };

    /**
     * @ngdoc function
     * @name ui.router.state.$state#includes
     * @methodOf ui.router.state.$state
     *
     * @description
     * A method to determine if the current active state is equal to or is the child of the
     * state stateName. If any params are passed then they will be tested for a match as well.
     * Not all the parameters need to be passed, just the ones you'd like to test for equality.
     *
     * @example
     * Partial and relative names
     * <pre>
     * $state.$current.name = 'contacts.details.item';
     *
     * // Using partial names
     * $state.includes("contacts"); // returns true
     * $state.includes("contacts.details"); // returns true
     * $state.includes("contacts.details.item"); // returns true
     * $state.includes("contacts.list"); // returns false
     * $state.includes("about"); // returns false
     *
     * // Using relative names (. and ^), typically from a template
     * // E.g. from the 'contacts.details' template
     * <div ng-class="{highlighted: $state.includes('.item')}">Item</div>
     * </pre>
     *
     * Basic globbing patterns
     * <pre>
     * $state.$current.name = 'contacts.details.item.url';
     *
     * $state.includes("*.details.*.*"); // returns true
     * $state.includes("*.details.**"); // returns true
     * $state.includes("**.item.**"); // returns true
     * $state.includes("*.details.item.url"); // returns true
     * $state.includes("*.details.*.url"); // returns true
     * $state.includes("*.details.*"); // returns false
     * $state.includes("item.**"); // returns false
     * </pre>
     *
     * @param {string} stateOrName A partial name, relative name, or glob pattern
     * to be searched for within the current state name.
     * @param {object=} params A param object, e.g. `{sectionId: section.id}`,
     * that you'd like to test against the current active state.
     * @param {object=} options An options object.  The options are:
     *
     * - **`relative`** - {string|object=} -  If `stateOrName` is a relative state reference and `options.relative` is set,
     * .includes will test relative to `options.relative` state (or name).
     *
     * @returns {boolean} Returns true if it does include the state
     */
    $state.includes = function includes(stateOrName, params, options) {
      options = extend({ relative: $state.$current }, options || {});
      if (isString(stateOrName) && isGlob(stateOrName)) {
        if (!doesStateMatchGlob(stateOrName)) {
          return false;
        }
        stateOrName = $state.$current.name;
      }

      var state = findState(stateOrName, options.relative);
      if (!isDefined(state)) { return undefined; }
      if (!isDefined($state.$current.includes[state.name])) { return false; }
      return params ? equalForKeys(state.params.$$values(params), $stateParams, objectKeys(params)) : true;
    };


    /**
     * @ngdoc function
     * @name ui.router.state.$state#href
     * @methodOf ui.router.state.$state
     *
     * @description
     * A url generation method that returns the compiled url for the given state populated with the given params.
     *
     * @example
     * <pre>
     * expect($state.href("about.person", { person: "bob" })).toEqual("/about/bob");
     * </pre>
     *
     * @param {string|object} stateOrName The state name or state object you'd like to generate a url from.
     * @param {object=} params An object of parameter values to fill the state's required parameters.
     * @param {object=} options Options object. The options are:
     *
     * - **`lossy`** - {boolean=true} -  If true, and if there is no url associated with the state provided in the
     *    first parameter, then the constructed href url will be built from the first navigable ancestor (aka
     *    ancestor with a valid url).
     * - **`inherit`** - {boolean=true}, If `true` will inherit url parameters from current url.
     * - **`relative`** - {object=$state.$current}, When transitioning with relative path (e.g '^'), 
     *    defines which state to be relative from.
     * - **`absolute`** - {boolean=false},  If true will generate an absolute url, e.g. "http://www.example.com/fullurl".
     * 
     * @returns {string} compiled state url
     */
    $state.href = function href(stateOrName, params, options) {
      options = extend({
        lossy:    true,
        inherit:  true,
        absolute: false,
        relative: $state.$current
      }, options || {});

      var state = findState(stateOrName, options.relative);

      if (!isDefined(state)) return null;
      if (options.inherit) params = inheritParams($stateParams, params || {}, $state.$current, state);
      
      var nav = (state && options.lossy) ? state.navigable : state;

      if (!nav || nav.url === undefined || nav.url === null) {
        return null;
      }
      return $urlRouter.href(nav.url, filterByKeys(state.params.$$keys().concat('#'), params || {}), {
        absolute: options.absolute
      });
    };

    /**
     * @ngdoc function
     * @name ui.router.state.$state#get
     * @methodOf ui.router.state.$state
     *
     * @description
     * Returns the state configuration object for any specific state or all states.
     *
     * @param {string|object=} stateOrName (absolute or relative) If provided, will only get the config for
     * the requested state. If not provided, returns an array of ALL state configs.
     * @param {string|object=} context When stateOrName is a relative state reference, the state will be retrieved relative to context.
     * @returns {Object|Array} State configuration object or array of all objects.
     */
    $state.get = function (stateOrName, context) {
      if (arguments.length === 0) return map(objectKeys(states), function(name) { return states[name].self; });
      var state = findState(stateOrName, context || $state.$current);
      return (state && state.self) ? state.self : null;
    };

    function resolveState(state, params, paramsAreFiltered, inherited, dst, options) {
      // Make a restricted $stateParams with only the parameters that apply to this state if
      // necessary. In addition to being available to the controller and onEnter/onExit callbacks,
      // we also need $stateParams to be available for any $injector calls we make during the
      // dependency resolution process.
      var $stateParams = (paramsAreFiltered) ? params : filterByKeys(state.params.$$keys(), params);
      var locals = { $stateParams: $stateParams };

      // Resolve 'global' dependencies for the state, i.e. those not specific to a view.
      // We're also including $stateParams in this; that way the parameters are restricted
      // to the set that should be visible to the state, and are independent of when we update
      // the global $state and $stateParams values.
      dst.resolve = $resolve.resolve(state.resolve, locals, dst.resolve, state);
      var promises = [dst.resolve.then(function (globals) {
        dst.globals = globals;
      })];
      if (inherited) promises.push(inherited);

      function resolveViews() {
        var viewsPromises = [];

        // Resolve template and dependencies for all views.
        forEach(state.views, function (view, name) {
          var injectables = (view.resolve && view.resolve !== state.resolve ? view.resolve : {});
          injectables.$template = [ function () {
            return $view.load(name, { view: view, locals: dst.globals, params: $stateParams, notify: options.notify }) || '';
          }];

          viewsPromises.push($resolve.resolve(injectables, dst.globals, dst.resolve, state).then(function (result) {
            // References to the controller (only instantiated at link time)
            if (isFunction(view.controllerProvider) || isArray(view.controllerProvider)) {
              var injectLocals = angular.extend({}, injectables, dst.globals);
              result.$$controller = $injector.invoke(view.controllerProvider, null, injectLocals);
            } else {
              result.$$controller = view.controller;
            }
            // Provide access to the state itself for internal use
            result.$$state = state;
            result.$$controllerAs = view.controllerAs;
            dst[name] = result;
          }));
        });

        return $q.all(viewsPromises).then(function(){
          return dst.globals;
        });
      }

      // Wait for all the promises and then return the activation object
      return $q.all(promises).then(resolveViews).then(function (values) {
        return dst;
      });
    }

    return $state;
  }

  function shouldSkipReload(to, toParams, from, fromParams, locals, options) {
    // Return true if there are no differences in non-search (path/object) params, false if there are differences
    function nonSearchParamsEqual(fromAndToState, fromParams, toParams) {
      // Identify whether all the parameters that differ between `fromParams` and `toParams` were search params.
      function notSearchParam(key) {
        return fromAndToState.params[key].location != "search";
      }
      var nonQueryParamKeys = fromAndToState.params.$$keys().filter(notSearchParam);
      var nonQueryParams = pick.apply({}, [fromAndToState.params].concat(nonQueryParamKeys));
      var nonQueryParamSet = new $$UMFP.ParamSet(nonQueryParams);
      return nonQueryParamSet.$$equals(fromParams, toParams);
    }

    // If reload was not explicitly requested
    // and we're transitioning to the same state we're already in
    // and    the locals didn't change
    //     or they changed in a way that doesn't merit reloading
    //        (reloadOnParams:false, or reloadOnSearch.false and only search params changed)
    // Then return true.
    if (!options.reload && to === from &&
      (locals === from.locals || (to.self.reloadOnSearch === false && nonSearchParamsEqual(from, fromParams, toParams)))) {
      return true;
    }
  }
}

angular.module('ui.router.state')
  .factory('$stateParams', function () { return {}; })
  .provider('$state', $StateProvider);


$ViewProvider.$inject = [];
function $ViewProvider() {

  this.$get = $get;
  /**
   * @ngdoc object
   * @name ui.router.state.$view
   *
   * @requires ui.router.util.$templateFactory
   * @requires $rootScope
   *
   * @description
   *
   */
  $get.$inject = ['$rootScope', '$templateFactory'];
  function $get(   $rootScope,   $templateFactory) {
    return {
      // $view.load('full.viewName', { template: ..., controller: ..., resolve: ..., async: false, params: ... })
      /**
       * @ngdoc function
       * @name ui.router.state.$view#load
       * @methodOf ui.router.state.$view
       *
       * @description
       *
       * @param {string} name name
       * @param {object} options option object.
       */
      load: function load(name, options) {
        var result, defaults = {
          template: null, controller: null, view: null, locals: null, notify: true, async: true, params: {}
        };
        options = extend(defaults, options);

        if (options.view) {
          result = $templateFactory.fromConfig(options.view, options.params, options.locals);
        }
        return result;
      }
    };
  }
}

angular.module('ui.router.state').provider('$view', $ViewProvider);

/**
 * @ngdoc object
 * @name ui.router.state.$uiViewScrollProvider
 *
 * @description
 * Provider that returns the {@link ui.router.state.$uiViewScroll} service function.
 */
function $ViewScrollProvider() {

  var useAnchorScroll = false;

  /**
   * @ngdoc function
   * @name ui.router.state.$uiViewScrollProvider#useAnchorScroll
   * @methodOf ui.router.state.$uiViewScrollProvider
   *
   * @description
   * Reverts back to using the core [`$anchorScroll`](http://docs.angularjs.org/api/ng.$anchorScroll) service for
   * scrolling based on the url anchor.
   */
  this.useAnchorScroll = function () {
    useAnchorScroll = true;
  };

  /**
   * @ngdoc object
   * @name ui.router.state.$uiViewScroll
   *
   * @requires $anchorScroll
   * @requires $timeout
   *
   * @description
   * When called with a jqLite element, it scrolls the element into view (after a
   * `$timeout` so the DOM has time to refresh).
   *
   * If you prefer to rely on `$anchorScroll` to scroll the view to the anchor,
   * this can be enabled by calling {@link ui.router.state.$uiViewScrollProvider#methods_useAnchorScroll `$uiViewScrollProvider.useAnchorScroll()`}.
   */
  this.$get = ['$anchorScroll', '$timeout', function ($anchorScroll, $timeout) {
    if (useAnchorScroll) {
      return $anchorScroll;
    }

    return function ($element) {
      return $timeout(function () {
        $element[0].scrollIntoView();
      }, 0, false);
    };
  }];
}

angular.module('ui.router.state').provider('$uiViewScroll', $ViewScrollProvider);

var ngMajorVer = angular.version.major;
var ngMinorVer = angular.version.minor;
/**
 * @ngdoc directive
 * @name ui.router.state.directive:ui-view
 *
 * @requires ui.router.state.$state
 * @requires $compile
 * @requires $controller
 * @requires $injector
 * @requires ui.router.state.$uiViewScroll
 * @requires $document
 *
 * @restrict ECA
 *
 * @description
 * The ui-view directive tells $state where to place your templates.
 *
 * @param {string=} name A view name. The name should be unique amongst the other views in the
 * same state. You can have views of the same name that live in different states.
 *
 * @param {string=} autoscroll It allows you to set the scroll behavior of the browser window
 * when a view is populated. By default, $anchorScroll is overridden by ui-router's custom scroll
 * service, {@link ui.router.state.$uiViewScroll}. This custom service let's you
 * scroll ui-view elements into view when they are populated during a state activation.
 *
 * @param {string=} noanimation If truthy, the non-animated renderer will be selected (no animations
 * will be applied to the ui-view)
 *
 * *Note: To revert back to old [`$anchorScroll`](http://docs.angularjs.org/api/ng.$anchorScroll)
 * functionality, call `$uiViewScrollProvider.useAnchorScroll()`.*
 *
 * @param {string=} onload Expression to evaluate whenever the view updates.
 * 
 * @example
 * A view can be unnamed or named. 
 * <pre>
 * <!-- Unnamed -->
 * <div ui-view></div> 
 * 
 * <!-- Named -->
 * <div ui-view="viewName"></div>
 * </pre>
 *
 * You can only have one unnamed view within any template (or root html). If you are only using a 
 * single view and it is unnamed then you can populate it like so:
 * <pre>
 * <div ui-view></div> 
 * $stateProvider.state("home", {
 *   template: "<h1>HELLO!</h1>"
 * })
 * </pre>
 * 
 * The above is a convenient shortcut equivalent to specifying your view explicitly with the {@link ui.router.state.$stateProvider#views `views`}
 * config property, by name, in this case an empty name:
 * <pre>
 * $stateProvider.state("home", {
 *   views: {
 *     "": {
 *       template: "<h1>HELLO!</h1>"
 *     }
 *   }    
 * })
 * </pre>
 * 
 * But typically you'll only use the views property if you name your view or have more than one view 
 * in the same template. There's not really a compelling reason to name a view if its the only one, 
 * but you could if you wanted, like so:
 * <pre>
 * <div ui-view="main"></div>
 * </pre> 
 * <pre>
 * $stateProvider.state("home", {
 *   views: {
 *     "main": {
 *       template: "<h1>HELLO!</h1>"
 *     }
 *   }    
 * })
 * </pre>
 * 
 * Really though, you'll use views to set up multiple views:
 * <pre>
 * <div ui-view></div>
 * <div ui-view="chart"></div> 
 * <div ui-view="data"></div> 
 * </pre>
 * 
 * <pre>
 * $stateProvider.state("home", {
 *   views: {
 *     "": {
 *       template: "<h1>HELLO!</h1>"
 *     },
 *     "chart": {
 *       template: "<chart_thing/>"
 *     },
 *     "data": {
 *       template: "<data_thing/>"
 *     }
 *   }    
 * })
 * </pre>
 *
 * Examples for `autoscroll`:
 *
 * <pre>
 * <!-- If autoscroll present with no expression,
 *      then scroll ui-view into view -->
 * <ui-view autoscroll/>
 *
 * <!-- If autoscroll present with valid expression,
 *      then scroll ui-view into view if expression evaluates to true -->
 * <ui-view autoscroll='true'/>
 * <ui-view autoscroll='false'/>
 * <ui-view autoscroll='scopeVariable'/>
 * </pre>
 */
$ViewDirective.$inject = ['$state', '$injector', '$uiViewScroll', '$interpolate'];
function $ViewDirective(   $state,   $injector,   $uiViewScroll,   $interpolate) {

  function getService() {
    return ($injector.has) ? function(service) {
      return $injector.has(service) ? $injector.get(service) : null;
    } : function(service) {
      try {
        return $injector.get(service);
      } catch (e) {
        return null;
      }
    };
  }

  var service = getService(),
      $animator = service('$animator'),
      $animate = service('$animate');

  // Returns a set of DOM manipulation functions based on which Angular version
  // it should use
  function getRenderer(attrs, scope) {
    var statics = {
      enter: function (element, target, cb) { target.after(element); cb(); },
      leave: function (element, cb) { element.remove(); cb(); }
    };

    if (!!attrs.noanimation) return statics;

    function animEnabled(element) {
      if (ngMajorVer === 1 && ngMinorVer >= 4) return !!$animate.enabled(element);
      if (ngMajorVer === 1 && ngMinorVer >= 2) return !!$animate.enabled();
      return (!!$animator);
    }

    // ng 1.2+
    if ($animate) {
      return {
        enter: function(element, target, cb) {
          if (!animEnabled(element)) {
            statics.enter(element, target, cb);
          } else if (angular.version.minor > 2) {
            $animate.enter(element, null, target).then(cb);
          } else {
            $animate.enter(element, null, target, cb);
          }
        },
        leave: function(element, cb) {
          if (!animEnabled(element)) {
            statics.leave(element, cb);
          } else if (angular.version.minor > 2) {
            $animate.leave(element).then(cb);
          } else {
            $animate.leave(element, cb);
          }
        }
      };
    }

    // ng 1.1.5
    if ($animator) {
      var animate = $animator && $animator(scope, attrs);

      return {
        enter: function(element, target, cb) {animate.enter(element, null, target); cb(); },
        leave: function(element, cb) { animate.leave(element); cb(); }
      };
    }

    return statics;
  }

  var directive = {
    restrict: 'ECA',
    terminal: true,
    priority: 400,
    transclude: 'element',
    compile: function (tElement, tAttrs, $transclude) {
      return function (scope, $element, attrs) {
        var previousEl, currentEl, currentScope, latestLocals,
            onloadExp     = attrs.onload || '',
            autoScrollExp = attrs.autoscroll,
            renderer      = getRenderer(attrs, scope);

        scope.$on('$stateChangeSuccess', function() {
          updateView(false);
        });

        updateView(true);

        function cleanupLastView() {
          var _previousEl = previousEl;
          var _currentScope = currentScope;

          if (_currentScope) {
            _currentScope._willBeDestroyed = true;
          }

          function cleanOld() {
            if (_previousEl) {
              _previousEl.remove();
            }

            if (_currentScope) {
              _currentScope.$destroy();
            }
          }

          if (currentEl) {
            renderer.leave(currentEl, function() {
              cleanOld();
              previousEl = null;
            });

            previousEl = currentEl;
          } else {
            cleanOld();
            previousEl = null;
          }

          currentEl = null;
          currentScope = null;
        }

        function updateView(firstTime) {
          var newScope,
              name            = getUiViewName(scope, attrs, $element, $interpolate),
              previousLocals  = name && $state.$current && $state.$current.locals[name];

          if (!firstTime && previousLocals === latestLocals || scope._willBeDestroyed) return; // nothing to do
          newScope = scope.$new();
          latestLocals = $state.$current.locals[name];

          /**
           * @ngdoc event
           * @name ui.router.state.directive:ui-view#$viewContentLoading
           * @eventOf ui.router.state.directive:ui-view
           * @eventType emits on ui-view directive scope
           * @description
           *
           * Fired once the view **begins loading**, *before* the DOM is rendered.
           *
           * @param {Object} event Event object.
           * @param {string} viewName Name of the view.
           */
          newScope.$emit('$viewContentLoading', name);

          var clone = $transclude(newScope, function(clone) {
            renderer.enter(clone, $element, function onUiViewEnter() {
              if(currentScope) {
                currentScope.$emit('$viewContentAnimationEnded');
              }

              if (angular.isDefined(autoScrollExp) && !autoScrollExp || scope.$eval(autoScrollExp)) {
                $uiViewScroll(clone);
              }
            });
            cleanupLastView();
          });

          currentEl = clone;
          currentScope = newScope;
          /**
           * @ngdoc event
           * @name ui.router.state.directive:ui-view#$viewContentLoaded
           * @eventOf ui.router.state.directive:ui-view
           * @eventType emits on ui-view directive scope
           * @description
           * Fired once the view is **loaded**, *after* the DOM is rendered.
           *
           * @param {Object} event Event object.
           * @param {string} viewName Name of the view.
           */
          currentScope.$emit('$viewContentLoaded', name);
          currentScope.$eval(onloadExp);
        }
      };
    }
  };

  return directive;
}

$ViewDirectiveFill.$inject = ['$compile', '$controller', '$state', '$interpolate'];
function $ViewDirectiveFill (  $compile,   $controller,   $state,   $interpolate) {
  return {
    restrict: 'ECA',
    priority: -400,
    compile: function (tElement) {
      var initial = tElement.html();
      return function (scope, $element, attrs) {
        var current = $state.$current,
            name = getUiViewName(scope, attrs, $element, $interpolate),
            locals  = current && current.locals[name];

        if (! locals) {
          return;
        }

        $element.data('$uiView', { name: name, state: locals.$$state });
        $element.html(locals.$template ? locals.$template : initial);

        var link = $compile($element.contents());

        if (locals.$$controller) {
          locals.$scope = scope;
          locals.$element = $element;
          var controller = $controller(locals.$$controller, locals);
          if (locals.$$controllerAs) {
            scope[locals.$$controllerAs] = controller;
          }
          $element.data('$ngControllerController', controller);
          $element.children().data('$ngControllerController', controller);
        }

        link(scope);
      };
    }
  };
}

/**
 * Shared ui-view code for both directives:
 * Given scope, element, and its attributes, return the view's name
 */
function getUiViewName(scope, attrs, element, $interpolate) {
  var name = $interpolate(attrs.uiView || attrs.name || '')(scope);
  var inherited = element.inheritedData('$uiView');
  return name.indexOf('@') >= 0 ?  name :  (name + '@' + (inherited ? inherited.state.name : ''));
}

angular.module('ui.router.state').directive('uiView', $ViewDirective);
angular.module('ui.router.state').directive('uiView', $ViewDirectiveFill);

function parseStateRef(ref, current) {
  var preparsed = ref.match(/^\s*({[^}]*})\s*$/), parsed;
  if (preparsed) ref = current + '(' + preparsed[1] + ')';
  parsed = ref.replace(/\n/g, " ").match(/^([^(]+?)\s*(\((.*)\))?$/);
  if (!parsed || parsed.length !== 4) throw new Error("Invalid state ref '" + ref + "'");
  return { state: parsed[1], paramExpr: parsed[3] || null };
}

function stateContext(el) {
  var stateData = el.parent().inheritedData('$uiView');

  if (stateData && stateData.state && stateData.state.name) {
    return stateData.state;
  }
}

function getTypeInfo(el) {
  // SVGAElement does not use the href attribute, but rather the 'xlinkHref' attribute.
  var isSvg = Object.prototype.toString.call(el.prop('href')) === '[object SVGAnimatedString]';
  var isForm = el[0].nodeName === "FORM";

  return {
    attr: isForm ? "action" : (isSvg ? 'xlink:href' : 'href'),
    isAnchor: el.prop("tagName").toUpperCase() === "A",
    clickable: !isForm
  };
}

function clickHook(el, $state, $timeout, type, current) {
  return function(e) {
    var button = e.which || e.button, target = current();

    if (!(button > 1 || e.ctrlKey || e.metaKey || e.shiftKey || el.attr('target'))) {
      // HACK: This is to allow ng-clicks to be processed before the transition is initiated:
      var transition = $timeout(function() {
        $state.go(target.state, target.params, target.options);
      });
      e.preventDefault();

      // if the state has no URL, ignore one preventDefault from the <a> directive.
      var ignorePreventDefaultCount = type.isAnchor && !target.href ? 1: 0;

      e.preventDefault = function() {
        if (ignorePreventDefaultCount-- <= 0) $timeout.cancel(transition);
      };
    }
  };
}

function defaultOpts(el, $state) {
  return { relative: stateContext(el) || $state.$current, inherit: true };
}

/**
 * @ngdoc directive
 * @name ui.router.state.directive:ui-sref
 *
 * @requires ui.router.state.$state
 * @requires $timeout
 *
 * @restrict A
 *
 * @description
 * A directive that binds a link (`<a>` tag) to a state. If the state has an associated
 * URL, the directive will automatically generate & update the `href` attribute via
 * the {@link ui.router.state.$state#methods_href $state.href()} method. Clicking
 * the link will trigger a state transition with optional parameters.
 *
 * Also middle-clicking, right-clicking, and ctrl-clicking on the link will be
 * handled natively by the browser.
 *
 * You can also use relative state paths within ui-sref, just like the relative
 * paths passed to `$state.go()`. You just need to be aware that the path is relative
 * to the state that the link lives in, in other words the state that loaded the
 * template containing the link.
 *
 * You can specify options to pass to {@link ui.router.state.$state#go $state.go()}
 * using the `ui-sref-opts` attribute. Options are restricted to `location`, `inherit`,
 * and `reload`.
 *
 * @example
 * Here's an example of how you'd use ui-sref and how it would compile. If you have the
 * following template:
 * <pre>
 * <a ui-sref="home">Home</a> | <a ui-sref="about">About</a> | <a ui-sref="{page: 2}">Next page</a>
 *
 * <ul>
 *     <li ng-repeat="contact in contacts">
 *         <a ui-sref="contacts.detail({ id: contact.id })">{{ contact.name }}</a>
 *     </li>
 * </ul>
 * </pre>
 *
 * Then the compiled html would be (assuming Html5Mode is off and current state is contacts):
 * <pre>
 * <a href="#/home" ui-sref="home">Home</a> | <a href="#/about" ui-sref="about">About</a> | <a href="#/contacts?page=2" ui-sref="{page: 2}">Next page</a>
 *
 * <ul>
 *     <li ng-repeat="contact in contacts">
 *         <a href="#/contacts/1" ui-sref="contacts.detail({ id: contact.id })">Joe</a>
 *     </li>
 *     <li ng-repeat="contact in contacts">
 *         <a href="#/contacts/2" ui-sref="contacts.detail({ id: contact.id })">Alice</a>
 *     </li>
 *     <li ng-repeat="contact in contacts">
 *         <a href="#/contacts/3" ui-sref="contacts.detail({ id: contact.id })">Bob</a>
 *     </li>
 * </ul>
 *
 * <a ui-sref="home" ui-sref-opts="{reload: true}">Home</a>
 * </pre>
 *
 * @param {string} ui-sref 'stateName' can be any valid absolute or relative state
 * @param {Object} ui-sref-opts options to pass to {@link ui.router.state.$state#go $state.go()}
 */
$StateRefDirective.$inject = ['$state', '$timeout'];
function $StateRefDirective($state, $timeout) {
  return {
    restrict: 'A',
    require: ['?^uiSrefActive', '?^uiSrefActiveEq'],
    link: function(scope, element, attrs, uiSrefActive) {
      var ref    = parseStateRef(attrs.uiSref, $state.current.name);
      var def    = { state: ref.state, href: null, params: null };
      var type   = getTypeInfo(element);
      var active = uiSrefActive[1] || uiSrefActive[0];

      def.options = extend(defaultOpts(element, $state), attrs.uiSrefOpts ? scope.$eval(attrs.uiSrefOpts) : {});

      var update = function(val) {
        if (val) def.params = angular.copy(val);
        def.href = $state.href(ref.state, def.params, def.options);

        if (active) active.$$addStateInfo(ref.state, def.params);
        if (def.href !== null) attrs.$set(type.attr, def.href);
      };

      if (ref.paramExpr) {
        scope.$watch(ref.paramExpr, function(val) { if (val !== def.params) update(val); }, true);
        def.params = angular.copy(scope.$eval(ref.paramExpr));
      }
      update();

      if (!type.clickable) return;
      element.bind("click", clickHook(element, $state, $timeout, type, function() { return def; }));
    }
  };
}

/**
 * @ngdoc directive
 * @name ui.router.state.directive:ui-state
 *
 * @requires ui.router.state.uiSref
 *
 * @restrict A
 *
 * @description
 * Much like ui-sref, but will accept named $scope properties to evaluate for a state definition,
 * params and override options.
 *
 * @param {string} ui-state 'stateName' can be any valid absolute or relative state
 * @param {Object} ui-state-params params to pass to {@link ui.router.state.$state#href $state.href()}
 * @param {Object} ui-state-opts options to pass to {@link ui.router.state.$state#go $state.go()}
 */
$StateRefDynamicDirective.$inject = ['$state', '$timeout'];
function $StateRefDynamicDirective($state, $timeout) {
  return {
    restrict: 'A',
    require: ['?^uiSrefActive', '?^uiSrefActiveEq'],
    link: function(scope, element, attrs, uiSrefActive) {
      var type   = getTypeInfo(element);
      var active = uiSrefActive[1] || uiSrefActive[0];
      var group  = [attrs.uiState, attrs.uiStateParams || null, attrs.uiStateOpts || null];
      var watch  = '[' + group.map(function(val) { return val || 'null'; }).join(', ') + ']';
      var def    = { state: null, params: null, options: null, href: null };

      function runStateRefLink (group) {
        def.state = group[0]; def.params = group[1]; def.options = group[2];
        def.href = $state.href(def.state, def.params, def.options);

        if (active) active.$$addStateInfo(def.state, def.params);
        if (def.href) attrs.$set(type.attr, def.href);
      }

      scope.$watch(watch, runStateRefLink, true);
      runStateRefLink(scope.$eval(watch));

      if (!type.clickable) return;
      element.bind("click", clickHook(element, $state, $timeout, type, function() { return def; }));
    }
  };
}


/**
 * @ngdoc directive
 * @name ui.router.state.directive:ui-sref-active
 *
 * @requires ui.router.state.$state
 * @requires ui.router.state.$stateParams
 * @requires $interpolate
 *
 * @restrict A
 *
 * @description
 * A directive working alongside ui-sref to add classes to an element when the
 * related ui-sref directive's state is active, and removing them when it is inactive.
 * The primary use-case is to simplify the special appearance of navigation menus
 * relying on `ui-sref`, by having the "active" state's menu button appear different,
 * distinguishing it from the inactive menu items.
 *
 * ui-sref-active can live on the same element as ui-sref or on a parent element. The first
 * ui-sref-active found at the same level or above the ui-sref will be used.
 *
 * Will activate when the ui-sref's target state or any child state is active. If you
 * need to activate only when the ui-sref target state is active and *not* any of
 * it's children, then you will use
 * {@link ui.router.state.directive:ui-sref-active-eq ui-sref-active-eq}
 *
 * @example
 * Given the following template:
 * <pre>
 * <ul>
 *   <li ui-sref-active="active" class="item">
 *     <a href ui-sref="app.user({user: 'bilbobaggins'})">@bilbobaggins</a>
 *   </li>
 * </ul>
 * </pre>
 *
 *
 * When the app state is "app.user" (or any children states), and contains the state parameter "user" with value "bilbobaggins",
 * the resulting HTML will appear as (note the 'active' class):
 * <pre>
 * <ul>
 *   <li ui-sref-active="active" class="item active">
 *     <a ui-sref="app.user({user: 'bilbobaggins'})" href="/users/bilbobaggins">@bilbobaggins</a>
 *   </li>
 * </ul>
 * </pre>
 *
 * The class name is interpolated **once** during the directives link time (any further changes to the
 * interpolated value are ignored).
 *
 * Multiple classes may be specified in a space-separated format:
 * <pre>
 * <ul>
 *   <li ui-sref-active='class1 class2 class3'>
 *     <a ui-sref="app.user">link</a>
 *   </li>
 * </ul>
 * </pre>
 *
 * It is also possible to pass ui-sref-active an expression that evaluates
 * to an object hash, whose keys represent active class names and whose
 * values represent the respective state names/globs.
 * ui-sref-active will match if the current active state **includes** any of
 * the specified state names/globs, even the abstract ones.
 *
 * @Example
 * Given the following template, with "admin" being an abstract state:
 * <pre>
 * <div ui-sref-active="{'active': 'admin.*'}">
 *   <a ui-sref-active="active" ui-sref="admin.roles">Roles</a>
 * </div>
 * </pre>
 *
 * When the current state is "admin.roles" the "active" class will be applied
 * to both the <div> and <a> elements. It is important to note that the state
 * names/globs passed to ui-sref-active shadow the state provided by ui-sref.
 */

/**
 * @ngdoc directive
 * @name ui.router.state.directive:ui-sref-active-eq
 *
 * @requires ui.router.state.$state
 * @requires ui.router.state.$stateParams
 * @requires $interpolate
 *
 * @restrict A
 *
 * @description
 * The same as {@link ui.router.state.directive:ui-sref-active ui-sref-active} but will only activate
 * when the exact target state used in the `ui-sref` is active; no child states.
 *
 */
$StateRefActiveDirective.$inject = ['$state', '$stateParams', '$interpolate'];
function $StateRefActiveDirective($state, $stateParams, $interpolate) {
  return  {
    restrict: "A",
    controller: ['$scope', '$element', '$attrs', '$timeout', function ($scope, $element, $attrs, $timeout) {
      var states = [], activeClasses = {}, activeEqClass, uiSrefActive;

      // There probably isn't much point in $observing this
      // uiSrefActive and uiSrefActiveEq share the same directive object with some
      // slight difference in logic routing
      activeEqClass = $interpolate($attrs.uiSrefActiveEq || '', false)($scope);

      try {
        uiSrefActive = $scope.$eval($attrs.uiSrefActive);
      } catch (e) {
        // Do nothing. uiSrefActive is not a valid expression.
        // Fall back to using $interpolate below
      }
      uiSrefActive = uiSrefActive || $interpolate($attrs.uiSrefActive || '', false)($scope);
      if (isObject(uiSrefActive)) {
        forEach(uiSrefActive, function(stateOrName, activeClass) {
          if (isString(stateOrName)) {
            var ref = parseStateRef(stateOrName, $state.current.name);
            addState(ref.state, $scope.$eval(ref.paramExpr), activeClass);
          }
        });
      }

      // Allow uiSref to communicate with uiSrefActive[Equals]
      this.$$addStateInfo = function (newState, newParams) {
        // we already got an explicit state provided by ui-sref-active, so we
        // shadow the one that comes from ui-sref
        if (isObject(uiSrefActive) && states.length > 0) {
          return;
        }
        addState(newState, newParams, uiSrefActive);
        update();
      };

      $scope.$on('$stateChangeSuccess', update);

      function addState(stateName, stateParams, activeClass) {
        var state = $state.get(stateName, stateContext($element));
        var stateHash = createStateHash(stateName, stateParams);

        states.push({
          state: state || { name: stateName },
          params: stateParams,
          hash: stateHash
        });

        activeClasses[stateHash] = activeClass;
      }

      /**
       * @param {string} state
       * @param {Object|string} [params]
       * @return {string}
       */
      function createStateHash(state, params) {
        if (!isString(state)) {
          throw new Error('state should be a string');
        }
        if (isObject(params)) {
          return state + toJson(params);
        }
        params = $scope.$eval(params);
        if (isObject(params)) {
          return state + toJson(params);
        }
        return state;
      }

      // Update route state
      function update() {
        for (var i = 0; i < states.length; i++) {
          if (anyMatch(states[i].state, states[i].params)) {
            addClass($element, activeClasses[states[i].hash]);
          } else {
            removeClass($element, activeClasses[states[i].hash]);
          }

          if (exactMatch(states[i].state, states[i].params)) {
            addClass($element, activeEqClass);
          } else {
            removeClass($element, activeEqClass);
          }
        }
      }

      function addClass(el, className) { $timeout(function () { el.addClass(className); }); }
      function removeClass(el, className) { el.removeClass(className); }
      function anyMatch(state, params) { return $state.includes(state.name, params); }
      function exactMatch(state, params) { return $state.is(state.name, params); }

      update();
    }]
  };
}

angular.module('ui.router.state')
  .directive('uiSref', $StateRefDirective)
  .directive('uiSrefActive', $StateRefActiveDirective)
  .directive('uiSrefActiveEq', $StateRefActiveDirective)
  .directive('uiState', $StateRefDynamicDirective);

/**
 * @ngdoc filter
 * @name ui.router.state.filter:isState
 *
 * @requires ui.router.state.$state
 *
 * @description
 * Translates to {@link ui.router.state.$state#methods_is $state.is("stateName")}.
 */
$IsStateFilter.$inject = ['$state'];
function $IsStateFilter($state) {
  var isFilter = function (state, params) {
    return $state.is(state, params);
  };
  isFilter.$stateful = true;
  return isFilter;
}

/**
 * @ngdoc filter
 * @name ui.router.state.filter:includedByState
 *
 * @requires ui.router.state.$state
 *
 * @description
 * Translates to {@link ui.router.state.$state#methods_includes $state.includes('fullOrPartialStateName')}.
 */
$IncludedByStateFilter.$inject = ['$state'];
function $IncludedByStateFilter($state) {
  var includesFilter = function (state, params, options) {
    return $state.includes(state, params, options);
  };
  includesFilter.$stateful = true;
  return  includesFilter;
}

angular.module('ui.router.state')
  .filter('isState', $IsStateFilter)
  .filter('includedByState', $IncludedByStateFilter);
})(window, window.angular);
/**
 * angular-ui-utils - Swiss-Army-Knife of AngularJS tools (with no external dependencies!)
 * @version v0.2.3 - 2015-03-30
 * @link http://angular-ui.github.com
 * @license MIT License, http://www.opensource.org/licenses/MIT
 */
function uiUploader(n){"use strict";function r(n){for(var i=0;i<n.length;i++)t.files.push(n[i])}function u(){return t.files}function i(n){t.options=n;for(var i=0;i<t.files.length;i++){if(t.activeUploads==t.options.concurrency)break;t.files[i].active||s(t.files[i],t.options.url)}}function f(n){t.files.splice(t.files.indexOf(n),1)}function e(){t.files.splice(0,t.files.length)}function o(n){var i=["n/a","bytes","KiB","MiB","GiB","TB","PB","EiB","ZiB","YiB"],t=+Math.floor(Math.log(n)/Math.log(1024));return(n/Math.pow(1024,t)).toFixed(t?1:0)+" "+i[isNaN(n)?0:t+1]}function s(n,r){var u,f,e,s="";if(t.activeUploads+=1,n.active=!0,u=new window.XMLHttpRequest,f=new window.FormData,u.open("POST",r),u.upload.onloadstart=function(){},u.upload.onprogress=function(i){if(i.lengthComputable){n.loaded=i.loaded;n.humanSize=o(i.loaded);t.options.onProgress(n)}},u.onload=function(){t.activeUploads-=1;i(t.options);t.options.onCompleted(n,u.responseText)},u.onerror=function(){},s)for(e in s)s.hasOwnProperty(e)&&f.append(e,s[e]);return f.append("file",n,n.name),u.send(f),u}var t=this;return t.files=[],t.options={},t.activeUploads=0,n.info("uiUploader loaded"),{addFiles:r,getFiles:u,files:t.files,startUpload:i,removeFile:f,removeAll:e}}angular.module("ui.alias",[]).config(["$compileProvider","uiAliasConfig",function(n,t){"use strict";t=t||{};angular.forEach(t,function(t,i){angular.isString(t)&&(t={replace:!0,template:t});n.directive(i,function(){return t})})}]);angular.module("ui.event",[]).directive("uiEvent",["$parse",function(n){"use strict";return function(t,i,r){var u=t.$eval(r.uiEvent);angular.forEach(u,function(r,u){var f=n(r);i.bind(u,function(n){var i=Array.prototype.slice.call(arguments);i=i.splice(1);f(t,{$event:n,$params:i});t.$$phase||t.$apply()})})}}]);angular.module("ui.format",[]).filter("format",function(){"use strict";return function(n,t){var i=n,r,u;return angular.isString(i)&&t!==undefined&&(angular.isArray(t)||angular.isObject(t)||(t=[t]),angular.isArray(t)?(r=t.length,u=function(n,i){return i=parseInt(i,10),i>=0&&i<r?t[i]:n},i=i.replace(/\$([0-9]+)/g,u)):angular.forEach(t,function(n,t){i=i.split(":"+t).join(n)})),i}});angular.module("ui.highlight",[]).filter("highlight",function(){"use strict";return function(n,t,i){return n&&(t||angular.isNumber(t))?(n=n.toString(),t=t.toString(),i?n.split(t).join('<span class="ui-match">'+t+"<\/span>"):n.replace(new RegExp(t,"gi"),'<span class="ui-match">$&<\/span>')):n}});angular.module("ui.include",[]).directive("uiInclude",["$http","$templateCache","$anchorScroll","$compile",function(n,t,i,r){"use strict";return{restrict:"ECA",terminal:!0,compile:function(u,f){var o=f.uiInclude||f.src,s=f.fragment||"",h=f.onload||"",e=f.autoscroll;return function(u,f){function v(){var v=++l,y=u.$eval(o),p=u.$eval(s);y?n.get(y,{cache:t}).success(function(n){if(v===l){c&&c.$destroy();c=u.$new();var t;t=p?angular.element("<div/>").html(n).find(p):angular.element("<div/>").html(n).contents();f.html(t);r(t)(c);angular.isDefined(e)&&(!e||u.$eval(e))&&i();c.$emit("$includeContentLoaded");u.$eval(h)}}).error(function(){v===l&&a()}):a()}var l=0,c,a=function(){c&&(c.$destroy(),c=null);f.html("")};u.$watch(s,v);u.$watch(o,v)}}}}]);angular.module("ui.indeterminate",[]).directive("uiIndeterminate",[function(){"use strict";return{compile:function(n,t){return!t.type||t.type.toLowerCase()!=="checkbox"?angular.noop:function(n,t,i){n.$watch(i.uiIndeterminate,function(n){t[0].indeterminate=!!n})}}}}]);angular.module("ui.inflector",[]).filter("inflector",function(){"use strict";function n(n){return n=n.replace(/([A-Z])|([\-|\_])/g,function(n,t){return" "+(t||"")}),n.replace(/\s\s+/g," ").trim().toLowerCase().split(" ")}function t(n){var t=[];return angular.forEach(n,function(n){t.push(n.charAt(0).toUpperCase()+n.substr(1))}),t}var i={humanize:function(i){return t(n(i)).join(" ")},underscore:function(t){return n(t).join("_")},variable:function(i){return i=n(i),i[0]+t(i.slice(1)).join("")}};return function(n,t){return t!==!1&&angular.isString(n)?(t=t||"humanize",i[t](n)):n}});angular.module("ui.jq",[]).value("uiJqConfig",{}).directive("uiJq",["uiJqConfig","$timeout",function(n,t){"use strict";return{restrict:"A",compile:function(i,r){if(!angular.isFunction(i[r.uiJq]))throw new Error('ui-jq: The "'+r.uiJq+'" function does not exist');var u=n&&n[r.uiJq];return function(n,i,r){function e(){var t=[];return r.uiOptions?(t=n.$eval("["+r.uiOptions+"]"),angular.isObject(u)&&angular.isObject(t[0])&&(t[0]=angular.extend({},u,t[0]))):u&&(t=[u]),t}function f(){t(function(){i[r.uiJq].apply(i,e())},0,!1)}r.ngModel&&i.is("select,input,textarea")&&i.bind("change",function(){i.trigger("input")});r.uiRefresh&&n.$watch(r.uiRefresh,function(){f()});f()}}}}]);angular.module("ui.keypress",[]).factory("keypressHelper",["$parse",function(n){"use strict";var t={8:"backspace",9:"tab",13:"enter",27:"esc",32:"space",33:"pageup",34:"pagedown",35:"end",36:"home",37:"left",38:"up",39:"right",40:"down",45:"insert",46:"delete"},i=function(n){return n.charAt(0).toUpperCase()+n.slice(1)};return function(r,u,f,e){var o,s=[];o=u.$eval(e["ui"+i(r)]);angular.forEach(o,function(t,i){var r,u;u=n(t);angular.forEach(i.split(" "),function(n){r={expression:u,keys:{}};angular.forEach(n.split("-"),function(n){r.keys[n]=!0});s.push(r)})});f.bind(r,function(n){var e=!!(n.metaKey&&!n.ctrlKey),o=!!n.altKey,h=!!n.ctrlKey,f=!!n.shiftKey,i=n.keyCode;r==="keypress"&&!f&&i>=97&&i<=122&&(i=i-32);angular.forEach(s,function(r){var s=r.keys[t[i]]||r.keys[i.toString()],c=!!r.keys.meta,l=!!r.keys.alt,a=!!r.keys.ctrl,v=!!r.keys.shift;s&&c===e&&l===o&&a===h&&v===f&&u.$apply(function(){r.expression(u,{$event:n})})})})}}]);angular.module("ui.keypress").directive("uiKeydown",["keypressHelper",function(n){"use strict";return{link:function(t,i,r){n("keydown",t,i,r)}}}]);angular.module("ui.keypress").directive("uiKeypress",["keypressHelper",function(n){"use strict";return{link:function(t,i,r){n("keypress",t,i,r)}}}]);angular.module("ui.keypress").directive("uiKeyup",["keypressHelper",function(n){"use strict";return{link:function(t,i,r){n("keyup",t,i,r)}}}]);angular.module("ui.mask",[]).value("uiMaskConfig",{maskDefinitions:{"9":/\d/,A:/[a-zA-Z]/,"*":/[a-zA-Z0-9]/},clearOnBlur:!0}).directive("uiMask",["uiMaskConfig","$parse",function(n,t){"use strict";return{priority:100,require:"ngModel",restrict:"A",compile:function(){var i=n;return function(n,r,u,f){function vt(n){return angular.isDefined(n)?(ti(n),!l)?lt():(bt(),kt(),!0):lt()}function yt(n){angular.isDefined(n)&&(c=n,l&&a())}function pt(n){return l?(e=p(n||""),s=ut(e),f.$setValidity("mask",s),s&&e.length?w(e):undefined):n}function wt(n){return l?(e=p(n||""),s=ut(e),f.$viewValue=e.length?w(e):"",f.$setValidity("mask",s),e===""&&u.required&&f.$setValidity("required",!f.$error.required),s?e:undefined):n}function lt(){return l=!1,dt(),angular.isDefined(ht)?r.attr("placeholder",ht):r.removeAttr("placeholder"),angular.isDefined(ct)?r.attr("maxlength",ct):r.removeAttr("maxlength"),r.val(f.$modelValue),f.$viewValue=f.$modelValue,!1}function bt(){e=tt=p(f.$viewValue||"");g=nt=w(e);s=ut(e);var n=s&&e.length?g:"";u.maxlength&&r.attr("maxlength",o[o.length-1]*2);r.attr("placeholder",c);r.val(n);f.$viewValue=n}function kt(){v||(r.bind("blur",at),r.bind("mousedown mouseup",b),r.bind("input keyup click focus",a),v=!0)}function dt(){v&&(r.unbind("blur",at),r.unbind("mousedown",b),r.unbind("mouseup",b),r.unbind("input",a),r.unbind("keyup",a),r.unbind("click",a),r.unbind("focus",a),v=!1)}function ut(n){return n.length?n.length>=d:!0}function p(n){var i="",t=k.slice();return n=n.toString(),angular.forEach(st,function(t){n=n.replace(t,"")}),angular.forEach(n.split(""),function(n){t.length&&t[0].test(n)&&(i+=n,t.shift())}),i}function w(n){var t="",i=o.slice();return angular.forEach(c.split(""),function(r,u){n.length&&u===i[0]?(t+=n.charAt(0)||"_",n=n.substr(1),i.shift()):t+=r}),t}function gt(n){var t=u.placeholder;return typeof t!="undefined"&&t[n]?t[n]:"_"}function ni(){return c.replace(/[_]+/g,"_").replace(/([^_]+)([a-zA-Z0-9])([^_])/g,"$1$2_$3").split("_")}function ti(n){var t=0;if(o=[],k=[],c="",typeof n=="string"){d=0;var i=!1,r=0,u=n.split("");angular.forEach(u,function(n,u){h.maskDefinitions[n]?(o.push(t),c+=gt(u-r),k.push(h.maskDefinitions[n]),t++,i||d++):n==="?"?(i=!0,r++):(c+=n,t++)})}o.push(o.slice().pop()+1);st=ni();l=o.length>1?!0:!1}function at(){h.clearOnBlur&&(it=0,y=0,s&&e.length!==0||(g="",r.val(""),n.$apply(function(){f.$setViewValue("")})))}function b(n){n.type==="mousedown"?r.bind("mouseout",ft):r.unbind("mouseout",ft)}function ft(){y=ot(this);r.unbind("mouseout",ft)}function a(t){var u,s,g;if(t=t||{},u=t.which,s=t.type,u!==16&&u!==91){var c=r.val(),v=nt,b,e=p(c),lt=tt,rt=!1,i=ii(this)||0,ut=it||0,ft=i-ut,h=o[0],l=o[e.length]||o.slice().shift(),a=y||0,at=ot(this)>0,st=a>0,ht=c.length>v.length||a&&c.length>v.length-a,k=c.length<v.length||a&&c.length===v.length-a,vt=u>=37&&u<=40&&t.shiftKey,yt=u===37,ct=u===8||s!=="keyup"&&k&&ft===-1,pt=u===46||s!=="keyup"&&k&&ft===0&&!st,d=(yt||ct||s==="click")&&i>h;if(y=ot(this),!vt&&(!at||s!=="click"&&s!=="keyup")){if(s==="input"&&k&&!st&&e===lt){while(ct&&i>h&&!et(i))i--;while(pt&&i<l&&o.indexOf(i)===-1)i++;g=o.indexOf(i);e=e.substring(0,g)+e.substring(g+1);rt=!0}for(b=w(e),nt=b,tt=e,r.val(b),rt&&n.$apply(function(){f.$setViewValue(e)}),ht&&i<=h&&(i=h+1),d&&i--,i=i>l?l:i<h?h:i;!et(i)&&i>h&&i<l;)i+=d?-1:1;(d&&i<l||ht&&!et(ut))&&i++;it=i;ri(this,i)}}}function et(n){return o.indexOf(n)>-1}function ii(n){if(!n)return 0;if(n.selectionStart!==undefined)return n.selectionStart;if(document.selection){n.focus();var t=document.selection.createRange();return t.moveStart("character",n.value?-n.value.length:0),t.text.length}return 0}function ri(n,t){if(!n)return 0;if(n.offsetWidth!==0&&n.offsetHeight!==0)if(n.setSelectionRange)n.focus(),n.setSelectionRange(t,t);else if(n.createTextRange){var i=n.createTextRange();i.collapse(!0);i.moveEnd("character",t);i.moveStart("character",t);i.select()}}function ot(n){return n?n.selectionStart!==undefined?n.selectionEnd-n.selectionStart:document.selection?document.selection.createRange().text.length:0:0}var l=!1,v=!1,o,k,c,st,d,e,g,s,ht=u.placeholder,ct=u.maxlength,nt,tt,it,y,h={},rt;u.uiOptions?(h=n.$eval("["+u.uiOptions+"]"),angular.isObject(h[0])&&(h=function(n,t){for(var i in n)Object.prototype.hasOwnProperty.call(n,i)&&(t[i]===undefined?t[i]=angular.copy(n[i]):angular.extend(t[i],n[i]));return t}(i,h[0]))):h=i;u.$observe("uiMask",vt);u.$observe("placeholder",yt);rt=!1;u.$observe("modelViewValue",function(n){n==="true"&&(rt=!0)});n.$watch(u.ngModel,function(i){if(rt&&i){var r=t(u.ngModel);r.assign(n,f.$viewValue)}});f.$formatters.push(pt);f.$parsers.push(wt);r.bind("mousedown mouseup",b);Array.prototype.indexOf||(Array.prototype.indexOf=function(n){var u,r,t,i;if(this===null)throw new TypeError;if((u=Object(this),r=u.length>>>0,r===0)||(t=0,arguments.length>1&&(t=Number(arguments[1]),t!==t?t=0:t!==0&&t!==Infinity&&t!==-Infinity&&(t=(t>0||-1)*Math.floor(Math.abs(t)))),t>=r))return-1;for(i=t>=0?t:Math.max(r-Math.abs(t),0);i<r;i++)if(i in u&&u[i]===n)return i;return-1})}}}}]);angular.module("ui.reset",[]).value("uiResetConfig",null).directive("uiReset",["uiResetConfig",function(n){"use strict";var t=null;return n!==undefined&&(t=n),{require:"ngModel",link:function(n,i,r,u){var f;f=angular.element('<a class="ui-reset" />');i.wrap('<span class="ui-resetwrap" />').after(f);f.bind("click",function(i){i.preventDefault();n.$apply(function(){r.uiReset?u.$setViewValue(n.$eval(r.uiReset)):u.$setViewValue(t);u.$render()})})}}}]);angular.module("ui.route",[]).directive("uiRoute",["$location","$parse",function(n,t){"use strict";return{restrict:"AC",scope:!0,compile:function(i,r){var u;if(r.uiRoute)u="uiRoute";else if(r.ngHref)u="ngHref";else if(r.href)u="href";else throw new Error("uiRoute missing a route or href property on "+i[0]);return function(i,r,f){function o(t){var r=t.indexOf("#");r>-1&&(t=t.substr(r+1));e=function(){s(i,n.path().indexOf(t)>-1)};e()}function h(t){var r=t.indexOf("#");r>-1&&(t=t.substr(r+1));e=function(){var r=new RegExp("^"+t+"$",["i"]);s(i,r.test(n.path()))};e()}var s=t(f.ngModel||f.routeModel||"$uiRoute").assign,e=angular.noop;switch(u){case"uiRoute":f.uiRoute?h(f.uiRoute):f.$observe("uiRoute",h);break;case"ngHref":f.ngHref?o(f.ngHref):f.$observe("ngHref",o);break;case"href":o(f.href)}i.$on("$routeChangeSuccess",function(){e()});i.$on("$stateChangeSuccess",function(){e()})}}}}]);angular.module("ui.scroll.jqlite",["ui.scroll"]).service("jqLiteExtras",["$log","$window",function(n,t){"use strict";return{registerFor:function(n){var i,u,s,r,f,e,o;return u=angular.element.prototype.css,n.prototype.css=function(n,t){var i,r;return r=this,i=r[0],(!i||i.nodeType===3||i.nodeType===8||!i.style)?void 0:u.call(r,n,t)},e=function(n){return n&&n.document&&n.location&&n.alert&&n.setInterval},o=function(n,t,i){var r,u,s,o,f;return r=n[0],f={top:["scrollTop","pageYOffset","scrollLeft"],left:["scrollLeft","pageXOffset","scrollTop"]}[t],u=f[0],o=f[1],s=f[2],e(r)?angular.isDefined(i)?r.scrollTo(n[s].call(n),i):o in r?r[o]:r.document.documentElement[u]:angular.isDefined(i)?r[u]=i:r[u]},t.getComputedStyle?(r=function(n){return t.getComputedStyle(n,null)},i=function(n,t){return parseFloat(t)}):(r=function(n){return n.currentStyle},i=function(n,t){var f,e,o,s,i,u,r;return f=/[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/.source,s=new RegExp("^("+f+")(?!px)[a-z%]+$","i"),s.test(t)?(r=n.style,e=r.left,i=n.runtimeStyle,u=i&&i.left,i&&(i.left=r.left),r.left=t,o=r.pixelLeft,r.left=e,u&&(i.left=u),o):parseFloat(t)}),s=function(n,t){var f,c,l,a,v,u,o,s,y,p,w,b,h;return e(n)?(f=document.documentElement[{height:"clientHeight",width:"clientWidth"}[t]],{base:f,padding:0,border:0,margin:0}):(h={width:[n.offsetWidth,"Left","Right"],height:[n.offsetHeight,"Top","Bottom"]}[t],f=h[0],o=h[1],s=h[2],u=r(n),w=i(n,u["padding"+o])||0,b=i(n,u["padding"+s])||0,c=i(n,u["border"+o+"Width"])||0,l=i(n,u["border"+s+"Width"])||0,a=u["margin"+o],v=u["margin"+s],y=i(n,a)||0,p=i(n,v)||0,{base:f,padding:w+b,border:c+l,margin:y+p})},f=function(n,t,i){var e,u,f;return u=s(n,t),u.base>0?{base:u.base-u.padding-u.border,outer:u.base,outerfull:u.base+u.margin}[i]:(e=r(n),f=e[t],(f<0||f===null)&&(f=n.style[t]||0),f=parseFloat(f)||0,{base:f-u.padding-u.border,outer:f,outerfull:f+u.padding+u.border+u.margin}[i])},angular.forEach({before:function(n){var t,u,f,e,o,i,r;if(o=this,u=o[0],e=o.parent(),t=e.contents(),t[0]===u)return e.prepend(n);for(f=i=1,r=t.length-1;1<=r?i<=r:i>=r;f=1<=r?++i:--i)if(t[f]===u){angular.element(t[f-1]).after(n);return}throw new Error("invalid DOM structure "+u.outerHTML);},height:function(n){var t;return t=this,angular.isDefined(n)?(angular.isNumber(n)&&(n=n+"px"),u.call(t,"height",n)):f(this[0],"height","base")},outerHeight:function(n){return f(this[0],"height",n?"outerfull":"outer")},offset:function(n){var u,t,i,r,f,e;if(f=this,arguments.length){if(n===void 0)return f;throw new Error("offset setter method is not implemented");}if(u={top:0,left:0},r=f[0],t=r&&r.ownerDocument,t)return i=t.documentElement,r.getBoundingClientRect!=null&&(u=r.getBoundingClientRect()),e=t.defaultView||t.parentWindow,{top:u.top+(e.pageYOffset||i.scrollTop)-(i.clientTop||0),left:u.left+(e.pageXOffset||i.scrollLeft)-(i.clientLeft||0)}},scrollTop:function(n){return o(this,"top",n)},scrollLeft:function(n){return o(this,"left",n)}},function(t,i){if(!n.prototype[i])return n.prototype[i]=t})}}}]).run(["$log","$window","jqLiteExtras",function(n,t,i){"use strict";if(!t.jQuery)return i.registerFor(angular.element)}]);angular.module("ui.scroll",[]).directive("uiScrollViewport",["$log",function(){"use strict";return{controller:["$scope","$element",function(n,t){return this.viewport=t,this}]}}]).directive("uiScroll",["$log","$injector","$rootScope","$timeout",function(n,t,i,r){"use strict";return{require:["?^uiScrollViewport"],transclude:"element",priority:1e3,terminal:!0,compile:function(u,f,e){return function(u,f,o,s){var v,ut,b,fi,pt,nt,wt,h,ft,tt,l,ei,oi,y,ct,bt,si,hi,ci,kt,it,lt,dt,et,c,ot,gt,st,ni,rt,ti,pi,at,p,k,li,w,ht,d,ai,g,ii,ri,vi,yi,vt,a,yt,ui;if(pi=n.debug||n.log,at=o.uiScroll.match(/^\s*(\w+)\s+in\s+([\w\.]+)\s*$/),!at)throw new Error("Expected uiScroll in form of '_item_ in _datasource_' but got '"+o.uiScroll+"'");if(rt=at[1],ct=at[2],ot=function(n,t){var i;if(n)return(i=t.match(/^([\w]+)\.(.+)$/),!i||i.length!==3)?n[t]:ot(n[i[1]],i[2])},g=function(n,t,i,r){var u;if(n&&t)return!(u=t.match(/^([\w]+)\.(.+)$/))&&t.indexOf(".")!==-1?void 0:!u||u.length!==3?(!angular.isObject(n[t])&&!r,n[t]=i):(angular.isObject(n[u[1]])||r||(n[u[1]]={}),g(n[u[1]],u[2],i,r))},y=ot(u,ct),ni=function(){return angular.isObject(y)&&typeof y.get=="function"},!ni()&&(y=t.get(ct),!ni()))throw new Error(""+ct+" is not a valid datasource");return tt=Math.max(3,+o.bufferSize||10),ft=function(){return a.outerHeight()*Math.max(.1,+o.padding||.1)},ai=function(n){var t;return(t=n[0].scrollHeight)!=null?t:n[0].document.documentElement.scrollHeight},l=null,e(u.$new(),function(n){var i,o,h,t,r,e;if(t=n[0].localName,t==="dl")throw new Error("ui-scroll directive does not support <"+n[0].localName+"> as a repeating tag: "+n[0].outerHTML);return t!=="li"&&t!=="tr"&&(t="div"),e=s[0]&&s[0].viewport?s[0].viewport:angular.element(window),e.css({"overflow-y":"auto",display:"block"}),h=function(n){var i,t,r;switch(n){case"tr":return r=angular.element("<table><tr><td><div><\/div><\/td><\/tr><\/table>"),i=r.find("div"),t=r.find("tr"),t.paddingHeight=function(){return i.height.apply(i,arguments)},t;default:return t=angular.element("<"+n+"><\/"+n+">"),t.paddingHeight=t.height,t}},o=function(n,t,i){return t[{top:"before",bottom:"after"}[i]](n),{paddingHeight:function(){return n.paddingHeight.apply(n,arguments)},insert:function(t){return n[{top:"after",bottom:"before"}[i]](t)}}},r=o(h(t),f,"top"),i=o(h(t),f,"bottom"),u.$on("$destroy",n.remove),l={viewport:e,topPadding:r.paddingHeight,bottomPadding:i.paddingHeight,append:i.insert,prepend:r.insert,bottomDataPos:function(){return ai(e)-i.paddingHeight()},topDataPos:function(){return r.paddingHeight()}}}),a=l.viewport,yt=a.scope()||i,yi=function(n){return v.topVisible=n.scope[rt],v.topVisibleElement=n.element,v.topVisibleScope=n.scope,o.topVisible&&g(yt,o.topVisible,v.topVisible),o.topVisibleElement&&g(yt,o.topVisibleElement,v.topVisibleElement),o.topVisibleScope&&g(yt,o.topVisibleScope,v.topVisibleScope),typeof y.topVisible=="function"?y.topVisible(n):void 0},ti=function(n){return v.isLoading=n,o.isLoading&&g(u,o.isLoading,n),typeof y.loading=="function"?y.loading(n):void 0},d=0,c=1,p=1,h=[],k=[],it=!1,nt=!1,w=function(n,t){for(var i,r=i=n;n<=t?i<t:i>t;r=n<=t?++i:--i)h[r].scope.$destroy(),h[r].element.remove();return h.splice(n,t-n)},li=function(){return d++,c=1,p=1,w(0,h.length),l.topPadding(0),l.bottomPadding(0),k=[],it=!1,nt=!1,b(d)},wt=function(){return a.scrollTop()+a.outerHeight()},vt=function(){return a.scrollTop()},ii=function(){return!it&&l.bottomDataPos()<wt()+ft()},ei=function(){var i,u,f,e,o,r,n,c,t,s;for(i=0,n=0,u=t=s=h.length-1;s<=0?t<=0:t>=0;u=s<=0?++t:--t)if(f=h[u],o=f.element.offset().top,r=c!==o,c=o,r&&(e=f.element.outerHeight(!0)),l.bottomDataPos()-i-e>wt()+ft())r&&(i+=e),n++,it=!1;else{if(r)break;n++}if(n>0)return l.bottomPadding(l.bottomPadding()+i),w(h.length-n,h.length),p-=n},ri=function(){return!nt&&l.topDataPos()>vt()-ft()},oi=function(){var u,f,e,t,n,o,i,r,s;for(i=0,n=0,r=0,s=h.length;r<s;r++)if(u=h[r],e=u.element.offset().top,t=o!==e,o=e,t&&(f=u.element.outerHeight(!0)),l.topDataPos()+i+f<vt()-ft())t&&(i+=f),n++,nt=!1;else{if(t)break;n++}if(n>0)return l.topPadding(l.topPadding()+i),w(0,n),c+=n},kt=function(n,t){return v.isLoading||ti(!0),k.push(t)===1?dt(n):void 0},gt=function(n){return n.displayTemp=n.css("display"),n.css("display","none")},vi=function(n){if(n.hasOwnProperty("displayTemp"))return n.css("display",n.displayTemp)},st=function(n,t){var i,f,r;return i=u.$new(),i[rt]=t,f=n>c,i.$index=n,f&&i.$index--,r={scope:i},e(i,function(t){return r.element=t,f?n===p?(gt(t),l.append(t),h.push(r)):(h[n-c].element.after(t),h.splice(n-c+1,0,r)):(gt(t),l.prepend(t),h.unshift(r))}),{appended:f,wrapper:r}},fi=function(n,t){var i;return n?l.bottomPadding(Math.max(0,l.bottomPadding()-t.element.outerHeight(!0))):(i=l.topPadding()-t.element.outerHeight(!0),i>=0?l.topPadding(i):a.scrollTop(a.scrollTop()+t.element.outerHeight(!0)))},bt=function(n,t){var i,f,e,r,c,o,u,a,s;if(ii()?kt(n,!0):ri()&&kt(n,!1),t&&t(n),k.length===0){for(o=0,s=[],u=0,a=h.length;u<a;u++)if(i=h[u],e=i.element.offset().top,r=c!==e,c=e,r&&(f=i.element.outerHeight(!0)),r&&l.topDataPos()+o+f<vt())s.push(o+=f);else{r&&yi(i);break}return s}},b=function(n,t,i){return t&&t.length?r(function(){var o,s,r,h,u,f,e,c,l;for(u=[],f=0,c=t.length;f<c;f++)r=t[f],o=r.wrapper.element,vi(o),s=o.offset().top,h!==s&&(u.push(r),h=s);for(e=0,l=u.length;e<l;e++)r=u[e],fi(r.appended,r.wrapper);return bt(n,i)}):bt(n,i)},et=function(n,t){return b(n,t,function(){return k.shift(),k.length===0?ti(!1):dt(n)})},dt=function(n){var t;return t=k[0],t?h.length&&!ii()?et(n):y.get(p,tt,function(t){var f,r,i,e;if((!n||n===d)&&!u.$$destroyed){if(r=[],t.length<tt&&(it=!0,l.bottomPadding(0)),t.length>0)for(oi(),i=0,e=t.length;i<e;i++)f=t[i],r.push(st(++p,f));return et(n,r)}}):h.length&&!ri()?et(n):y.get(c-tt,tt,function(t){var r,f,i,e;if((!n||n===d)&&!u.$$destroyed){if(f=[],t.length<tt&&(nt=!0,l.topPadding(0)),t.length>0)for(h.length&&ei(),r=i=e=t.length-1;e<=0?i<=0:i>=0;r=e<=0?++i:--i)f.unshift(st(--c,t[r]));return et(n,f)}})},ht=function(){if(!i.$$phase&&!v.isLoading)return b(),u.$apply()},ui=function(n){var t,i;return t=a[0].scrollTop,i=a[0].scrollHeight-a[0].clientHeight,t===0&&!nt||t===i&&!it?n.preventDefault():void 0},a.bind("resize",ht),a.bind("scroll",ht),a.bind("mousewheel",ui),u.$watch(y.revision,li),lt=y.scope?y.scope.$new():u.$new(),u.$on("$destroy",function(){for(var t,n=0,i=h.length;n<i;n++)t=h[n],t.scope.$destroy(),t.element.remove();return a.unbind("resize",ht),a.unbind("scroll",ht),a.unbind("mousewheel",ui)}),v={},v.isLoading=!1,pt=function(n,t){var i,r,u,f,a,e,o,s,l,v,y,b;if(r=[],angular.isArray(t))if(t.length){if(t.length===1&&t[0]===n.scope[rt])return r;for(f=n.scope.$index,e=f>c?f-c:1,i=o=0,v=t.length;o<v;i=++o)a=t[i],r.push(st(f+i,a));for(w(e,e+1),i=s=0,y=h.length;s<y;i=++s)u=h[i],u.scope.$index=c+i}else for(w(n.scope.$index-c,n.scope.$index-c+1),p--,i=l=0,b=h.length;l<b;i=++l)u=h[i],u.scope.$index=c+i;return r},v.applyUpdates=function(n,t){var i,r,u,e,f,o;if(i=[],d++,angular.isFunction(n))for(f=h.slice(0),u=0,e=f.length;u<e;u++)r=f[u],i.concat(i,pt(r,n(r.scope[rt],r.scope,r.element)));else if(n%1==0)0<=(o=n-c-1)&&o<h.length&&(i=pt(h[n-c],t));else throw new Error("applyUpdates - "+n+" is not a valid index or outside of range");return b(d,i)},o.adapter&&(ut=ot(u,o.adapter),ut||(g(u,o.adapter,{}),ut=ot(u,o.adapter)),angular.extend(ut,v),v=ut),ci=function(n,t){var r,u,i,f,e;if(angular.isFunction(n))for(u=function(t){return n(t.scope)},i=0,f=h.length;i<f;i++)r=h[i],u(r);else 0<=(e=n-c-1)&&e<h.length&&(h[n-c-1].scope[rt]=t);return null},si=function(n){var t,r,i,o,s,u,f,e,l,a,v,y;if(angular.isFunction(n)){for(i=[],u=0,l=h.length;u<l;u++)r=h[u],i.unshift(r);for(s=function(r){if(n(r.scope))return w(i.length-1-t,i.length-t),p--},t=f=0,a=i.length;f<a;t=++f)o=i[t],s(o)}else 0<=(y=n-c-1)&&y<h.length&&(w(n-c-1,n-c),p--);for(t=e=0,v=h.length;e<v;t=++e)r=h[t],r.scope.$index=c+t;return b()},hi=function(n,t){var i,r,u,f,e;if(r=[],angular.isFunction(n))throw new Error("not implemented - Insert with locator function");else 0<=(e=n-c-1)&&e<h.length&&(r.push(st(n,t)),p++);for(i=u=0,f=h.length;u<f;i=++u)t=h[i],t.scope.$index=c+i;return b(null,r)},lt.$on("insert.item",function(n,t,i){return hi(t,i)}),lt.$on("update.items",function(n,t,i){return ci(t,i)}),lt.$on("delete.items",function(n,t){return si(t)})}}}}]);angular.module("ui.scrollfix",[]).directive("uiScrollfix",["$window",function(n){"use strict";function t(){if(angular.isDefined(n.pageYOffset))return n.pageYOffset;var t=document.compatMode&&document.compatMode!=="BackCompat"?document.documentElement:document.body;return t.scrollTop}return{require:"^?uiScrollfixTarget",link:function(i,r,u,f){function c(){var n=e?u.uiScrollfix:r[0].offsetTop+o,i=f?h[0].scrollTop:t();!r.hasClass("ui-scrollfix")&&i>n?(r.addClass("ui-scrollfix"),s=n):r.hasClass("ui-scrollfix")&&i<s&&r.removeClass("ui-scrollfix")}var e=!0,o=0,s,h=f&&f.$element||angular.element(n);u.uiScrollfix?typeof u.uiScrollfix=="string"&&(u.uiScrollfix.charAt(0)==="-"?(e=!1,o=-parseFloat(u.uiScrollfix.substr(1))):u.uiScrollfix.charAt(0)==="+"&&(e=!1,o=parseFloat(u.uiScrollfix.substr(1)))):e=!1;s=e?u.uiScrollfix:r[0].offsetTop+o;h.on("scroll",c);i.$on("$destroy",function(){h.off("scroll",c)})}}}]).directive("uiScrollfixTarget",[function(){"use strict";return{controller:["$element",function(n){this.$element=n}]}}]);angular.module("ui.showhide",[]).directive("uiShow",[function(){"use strict";return function(n,t,i){n.$watch(i.uiShow,function(n){n?t.addClass("ui-show"):t.removeClass("ui-show")})}}]).directive("uiHide",[function(){"use strict";return function(n,t,i){n.$watch(i.uiHide,function(n){n?t.addClass("ui-hide"):t.removeClass("ui-hide")})}}]).directive("uiToggle",[function(){"use strict";return function(n,t,i){n.$watch(i.uiToggle,function(n){n?t.removeClass("ui-hide").addClass("ui-show"):t.removeClass("ui-show").addClass("ui-hide")})}}]);angular.module("ui.unique",[]).filter("unique",["$parse",function(n){"use strict";return function(t,i){if(i===!1)return t;if((i||angular.isUndefined(i))&&angular.isArray(t)){var r=[],f=angular.isString(i)?n(i):function(n){return n},u=function(n){return angular.isObject(n)?f(n):n};angular.forEach(t,function(n){for(var i=!1,t=0;t<r.length;t++)if(angular.equals(u(r[t]),u(n))){i=!0;break}i||r.push(n)});t=r}return t}}]);angular.module("ui.uploader",[]).service("uiUploader",uiUploader);uiUploader.$inject=["$log"];angular.module("ui.validate",[]).directive("uiValidate",function(){"use strict";return{restrict:"A",require:"ngModel",link:function(n,t,i,r){function o(t){if(angular.isString(t)){n.$watch(t,function(){angular.forEach(u,function(n){n(r.$modelValue)})});return}if(angular.isArray(t)){angular.forEach(t,function(t){n.$watch(t,function(){angular.forEach(u,function(n){n(r.$modelValue)})})});return}angular.isObject(t)&&angular.forEach(t,function(t,i){angular.isString(t)&&n.$watch(t,function(){u[i](r.$modelValue)});angular.isArray(t)&&angular.forEach(t,function(t){n.$watch(t,function(){u[i](r.$modelValue)})})})}var e,u={},f=n.$eval(i.uiValidate);f&&(angular.isString(f)&&(f={validator:f}),angular.forEach(f,function(t,i){e=function(u){var f=n.$eval(t,{$value:u});return angular.isObject(f)&&angular.isFunction(f.then)?(f.then(function(){r.$setValidity(i,!0)},function(){r.$setValidity(i,!1)}),u):f?(r.$setValidity(i,!0),u):(r.$setValidity(i,!1),u)};u[i]=e;r.$formatters.push(e);r.$parsers.push(e)}),i.uiValidateWatch&&o(n.$eval(i.uiValidateWatch)))}}});angular.module("ui.utils",["ui.event","ui.format","ui.highlight","ui.include","ui.indeterminate","ui.inflector","ui.jq","ui.keypress","ui.mask","ui.reset","ui.route","ui.scrollfix","ui.scroll","ui.scroll.jqlite","ui.showhide","ui.unique","ui.validate"]);
//# sourceMappingURL=ui-utils.min.js.map

/**
 * @license AngularJS v1.5.6
 * (c) 2010-2016 Google, Inc. http://angularjs.org
 * License: MIT
 */
(function(window, angular) {'use strict';

/* jshint ignore:start */
var noop        = angular.noop;
var copy        = angular.copy;
var extend      = angular.extend;
var jqLite      = angular.element;
var forEach     = angular.forEach;
var isArray     = angular.isArray;
var isString    = angular.isString;
var isObject    = angular.isObject;
var isUndefined = angular.isUndefined;
var isDefined   = angular.isDefined;
var isFunction  = angular.isFunction;
var isElement   = angular.isElement;

var ELEMENT_NODE = 1;
var COMMENT_NODE = 8;

var ADD_CLASS_SUFFIX = '-add';
var REMOVE_CLASS_SUFFIX = '-remove';
var EVENT_CLASS_PREFIX = 'ng-';
var ACTIVE_CLASS_SUFFIX = '-active';
var PREPARE_CLASS_SUFFIX = '-prepare';

var NG_ANIMATE_CLASSNAME = 'ng-animate';
var NG_ANIMATE_CHILDREN_DATA = '$$ngAnimateChildren';

// Detect proper transitionend/animationend event names.
var CSS_PREFIX = '', TRANSITION_PROP, TRANSITIONEND_EVENT, ANIMATION_PROP, ANIMATIONEND_EVENT;

// If unprefixed events are not supported but webkit-prefixed are, use the latter.
// Otherwise, just use W3C names, browsers not supporting them at all will just ignore them.
// Note: Chrome implements `window.onwebkitanimationend` and doesn't implement `window.onanimationend`
// but at the same time dispatches the `animationend` event and not `webkitAnimationEnd`.
// Register both events in case `window.onanimationend` is not supported because of that,
// do the same for `transitionend` as Safari is likely to exhibit similar behavior.
// Also, the only modern browser that uses vendor prefixes for transitions/keyframes is webkit
// therefore there is no reason to test anymore for other vendor prefixes:
// http://caniuse.com/#search=transition
if (isUndefined(window.ontransitionend) && isDefined(window.onwebkittransitionend)) {
  CSS_PREFIX = '-webkit-';
  TRANSITION_PROP = 'WebkitTransition';
  TRANSITIONEND_EVENT = 'webkitTransitionEnd transitionend';
} else {
  TRANSITION_PROP = 'transition';
  TRANSITIONEND_EVENT = 'transitionend';
}

if (isUndefined(window.onanimationend) && isDefined(window.onwebkitanimationend)) {
  CSS_PREFIX = '-webkit-';
  ANIMATION_PROP = 'WebkitAnimation';
  ANIMATIONEND_EVENT = 'webkitAnimationEnd animationend';
} else {
  ANIMATION_PROP = 'animation';
  ANIMATIONEND_EVENT = 'animationend';
}

var DURATION_KEY = 'Duration';
var PROPERTY_KEY = 'Property';
var DELAY_KEY = 'Delay';
var TIMING_KEY = 'TimingFunction';
var ANIMATION_ITERATION_COUNT_KEY = 'IterationCount';
var ANIMATION_PLAYSTATE_KEY = 'PlayState';
var SAFE_FAST_FORWARD_DURATION_VALUE = 9999;

var ANIMATION_DELAY_PROP = ANIMATION_PROP + DELAY_KEY;
var ANIMATION_DURATION_PROP = ANIMATION_PROP + DURATION_KEY;
var TRANSITION_DELAY_PROP = TRANSITION_PROP + DELAY_KEY;
var TRANSITION_DURATION_PROP = TRANSITION_PROP + DURATION_KEY;

var isPromiseLike = function(p) {
  return p && p.then ? true : false;
};

var ngMinErr = angular.$$minErr('ng');
function assertArg(arg, name, reason) {
  if (!arg) {
    throw ngMinErr('areq', "Argument '{0}' is {1}", (name || '?'), (reason || "required"));
  }
  return arg;
}

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 packageStyles(options) {
  var styles = {};
  if (options && (options.to || options.from)) {
    styles.to = options.to;
    styles.from = options.from;
  }
  return styles;
}

function pendClasses(classes, fix, isPrefix) {
  var className = '';
  classes = isArray(classes)
      ? classes
      : classes && isString(classes) && classes.length
          ? classes.split(/\s+/)
          : [];
  forEach(classes, function(klass, i) {
    if (klass && klass.length > 0) {
      className += (i > 0) ? ' ' : '';
      className += isPrefix ? fix + klass
                            : klass + fix;
    }
  });
  return className;
}

function removeFromArray(arr, val) {
  var index = arr.indexOf(val);
  if (val >= 0) {
    arr.splice(index, 1);
  }
}

function stripCommentsFromElement(element) {
  if (element instanceof jqLite) {
    switch (element.length) {
      case 0:
        return element;
        break;

      case 1:
        // there is no point of stripping anything if the element
        // is the only element within the jqLite wrapper.
        // (it's important that we retain the element instance.)
        if (element[0].nodeType === ELEMENT_NODE) {
          return element;
        }
        break;

      default:
        return jqLite(extractElementNode(element));
        break;
    }
  }

  if (element.nodeType === ELEMENT_NODE) {
    return jqLite(element);
  }
}

function extractElementNode(element) {
  if (!element[0]) return element;
  for (var i = 0; i < element.length; i++) {
    var elm = element[i];
    if (elm.nodeType == ELEMENT_NODE) {
      return elm;
    }
  }
}

function $$addClass($$jqLite, element, className) {
  forEach(element, function(elm) {
    $$jqLite.addClass(elm, className);
  });
}

function $$removeClass($$jqLite, element, className) {
  forEach(element, function(elm) {
    $$jqLite.removeClass(elm, className);
  });
}

function applyAnimationClassesFactory($$jqLite) {
  return function(element, options) {
    if (options.addClass) {
      $$addClass($$jqLite, element, options.addClass);
      options.addClass = null;
    }
    if (options.removeClass) {
      $$removeClass($$jqLite, element, options.removeClass);
      options.removeClass = null;
    }
  }
}

function prepareAnimationOptions(options) {
  options = options || {};
  if (!options.$$prepared) {
    var domOperation = options.domOperation || noop;
    options.domOperation = function() {
      options.$$domOperationFired = true;
      domOperation();
      domOperation = noop;
    };
    options.$$prepared = true;
  }
  return options;
}

function applyAnimationStyles(element, options) {
  applyAnimationFromStyles(element, options);
  applyAnimationToStyles(element, options);
}

function applyAnimationFromStyles(element, options) {
  if (options.from) {
    element.css(options.from);
    options.from = null;
  }
}

function applyAnimationToStyles(element, options) {
  if (options.to) {
    element.css(options.to);
    options.to = null;
  }
}

function mergeAnimationDetails(element, oldAnimation, newAnimation) {
  var target = oldAnimation.options || {};
  var newOptions = newAnimation.options || {};

  var toAdd = (target.addClass || '') + ' ' + (newOptions.addClass || '');
  var toRemove = (target.removeClass || '') + ' ' + (newOptions.removeClass || '');
  var classes = resolveElementClasses(element.attr('class'), toAdd, toRemove);

  if (newOptions.preparationClasses) {
    target.preparationClasses = concatWithSpace(newOptions.preparationClasses, target.preparationClasses);
    delete newOptions.preparationClasses;
  }

  // noop is basically when there is no callback; otherwise something has been set
  var realDomOperation = target.domOperation !== noop ? target.domOperation : null;

  extend(target, newOptions);

  // TODO(matsko or sreeramu): proper fix is to maintain all animation callback in array and call at last,but now only leave has the callback so no issue with this.
  if (realDomOperation) {
    target.domOperation = realDomOperation;
  }

  if (classes.addClass) {
    target.addClass = classes.addClass;
  } else {
    target.addClass = null;
  }

  if (classes.removeClass) {
    target.removeClass = classes.removeClass;
  } else {
    target.removeClass = null;
  }

  oldAnimation.addClass = target.addClass;
  oldAnimation.removeClass = target.removeClass;

  return target;
}

function resolveElementClasses(existing, toAdd, toRemove) {
  var ADD_CLASS = 1;
  var REMOVE_CLASS = -1;

  var flags = {};
  existing = splitClassesToLookup(existing);

  toAdd = splitClassesToLookup(toAdd);
  forEach(toAdd, function(value, key) {
    flags[key] = ADD_CLASS;
  });

  toRemove = splitClassesToLookup(toRemove);
  forEach(toRemove, function(value, key) {
    flags[key] = flags[key] === ADD_CLASS ? null : REMOVE_CLASS;
  });

  var classes = {
    addClass: '',
    removeClass: ''
  };

  forEach(flags, function(val, klass) {
    var prop, allow;
    if (val === ADD_CLASS) {
      prop = 'addClass';
      allow = !existing[klass];
    } else if (val === REMOVE_CLASS) {
      prop = 'removeClass';
      allow = existing[klass];
    }
    if (allow) {
      if (classes[prop].length) {
        classes[prop] += ' ';
      }
      classes[prop] += klass;
    }
  });

  function splitClassesToLookup(classes) {
    if (isString(classes)) {
      classes = classes.split(' ');
    }

    var obj = {};
    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;
  }

  return classes;
}

function getDomNode(element) {
  return (element instanceof angular.element) ? element[0] : element;
}

function applyGeneratedPreparationClasses(element, event, options) {
  var classes = '';
  if (event) {
    classes = pendClasses(event, EVENT_CLASS_PREFIX, true);
  }
  if (options.addClass) {
    classes = concatWithSpace(classes, pendClasses(options.addClass, ADD_CLASS_SUFFIX));
  }
  if (options.removeClass) {
    classes = concatWithSpace(classes, pendClasses(options.removeClass, REMOVE_CLASS_SUFFIX));
  }
  if (classes.length) {
    options.preparationClasses = classes;
    element.addClass(classes);
  }
}

function clearGeneratedClasses(element, options) {
  if (options.preparationClasses) {
    element.removeClass(options.preparationClasses);
    options.preparationClasses = null;
  }
  if (options.activeClasses) {
    element.removeClass(options.activeClasses);
    options.activeClasses = null;
  }
}

function blockTransitions(node, duration) {
  // we use a negative delay value since it performs blocking
  // yet it doesn't kill any existing transitions running on the
  // same element which makes this safe for class-based animations
  var value = duration ? '-' + duration + 's' : '';
  applyInlineStyle(node, [TRANSITION_DELAY_PROP, value]);
  return [TRANSITION_DELAY_PROP, value];
}

function blockKeyframeAnimations(node, applyBlock) {
  var value = applyBlock ? 'paused' : '';
  var key = ANIMATION_PROP + ANIMATION_PLAYSTATE_KEY;
  applyInlineStyle(node, [key, value]);
  return [key, value];
}

function applyInlineStyle(node, styleTuple) {
  var prop = styleTuple[0];
  var value = styleTuple[1];
  node.style[prop] = value;
}

function concatWithSpace(a,b) {
  if (!a) return b;
  if (!b) return a;
  return a + ' ' + b;
}

var $$rAFSchedulerFactory = ['$$rAF', function($$rAF) {
  var queue, cancelFn;

  function scheduler(tasks) {
    // we make a copy since RAFScheduler mutates the state
    // of the passed in array variable and this would be difficult
    // to track down on the outside code
    queue = queue.concat(tasks);
    nextTick();
  }

  queue = scheduler.queue = [];

  /* waitUntilQuiet does two things:
   * 1. It will run the FINAL `fn` value only when an uncanceled RAF has passed through
   * 2. It will delay the next wave of tasks from running until the quiet `fn` has run.
   *
   * The motivation here is that animation code can request more time from the scheduler
   * before the next wave runs. This allows for certain DOM properties such as classes to
   * be resolved in time for the next animation to run.
   */
  scheduler.waitUntilQuiet = function(fn) {
    if (cancelFn) cancelFn();

    cancelFn = $$rAF(function() {
      cancelFn = null;
      fn();
      nextTick();
    });
  };

  return scheduler;

  function nextTick() {
    if (!queue.length) return;

    var items = queue.shift();
    for (var i = 0; i < items.length; i++) {
      items[i]();
    }

    if (!cancelFn) {
      $$rAF(function() {
        if (!cancelFn) nextTick();
      });
    }
  }
}];

/**
 * @ngdoc directive
 * @name ngAnimateChildren
 * @restrict AE
 * @element ANY
 *
 * @description
 *
 * ngAnimateChildren allows you to specify that children of this element should animate even if any
 * of the children's parents are currently animating. By default, when an element has an active `enter`, `leave`, or `move`
 * (structural) animation, child elements that also have an active structural animation are not animated.
 *
 * Note that even if `ngAnimteChildren` is set, no child animations will run when the parent element is removed from the DOM (`leave` animation).
 *
 *
 * @param {string} ngAnimateChildren If the value is empty, `true` or `on`,
 *     then child animations are allowed. If the value is `false`, child animations are not allowed.
 *
 * @example
 * <example module="ngAnimateChildren" name="ngAnimateChildren" deps="angular-animate.js" animations="true">
     <file name="index.html">
       <div ng-controller="mainController as main">
         <label>Show container? <input type="checkbox" ng-model="main.enterElement" /></label>
         <label>Animate children? <input type="checkbox" ng-model="main.animateChildren" /></label>
         <hr>
         <div ng-animate-children="{{main.animateChildren}}">
           <div ng-if="main.enterElement" class="container">
             List of items:
             <div ng-repeat="item in [0, 1, 2, 3]" class="item">Item {{item}}</div>
           </div>
         </div>
       </div>
     </file>
     <file name="animations.css">

      .container.ng-enter,
      .container.ng-leave {
        transition: all ease 1.5s;
      }

      .container.ng-enter,
      .container.ng-leave-active {
        opacity: 0;
      }

      .container.ng-leave,
      .container.ng-enter-active {
        opacity: 1;
      }

      .item {
        background: firebrick;
        color: #FFF;
        margin-bottom: 10px;
      }

      .item.ng-enter,
      .item.ng-leave {
        transition: transform 1.5s ease;
      }

      .item.ng-enter {
        transform: translateX(50px);
      }

      .item.ng-enter-active {
        transform: translateX(0);
      }
    </file>
    <file name="script.js">
      angular.module('ngAnimateChildren', ['ngAnimate'])
        .controller('mainController', function() {
          this.animateChildren = false;
          this.enterElement = false;
        });
    </file>
  </example>
 */
var $$AnimateChildrenDirective = ['$interpolate', function($interpolate) {
  return {
    link: function(scope, element, attrs) {
      var val = attrs.ngAnimateChildren;
      if (angular.isString(val) && val.length === 0) { //empty attribute
        element.data(NG_ANIMATE_CHILDREN_DATA, true);
      } else {
        // Interpolate and set the value, so that it is available to
        // animations that run right after compilation
        setData($interpolate(val)(scope));
        attrs.$observe('ngAnimateChildren', setData);
      }

      function setData(value) {
        value = value === 'on' || value === 'true';
        element.data(NG_ANIMATE_CHILDREN_DATA, value);
      }
    }
  };
}];

var ANIMATE_TIMER_KEY = '$$animateCss';

/**
 * @ngdoc service
 * @name $animateCss
 * @kind object
 *
 * @description
 * The `$animateCss` service is a useful utility to trigger customized CSS-based transitions/keyframes
 * from a JavaScript-based animation or directly from a directive. The purpose of `$animateCss` is NOT
 * to side-step how `$animate` and ngAnimate work, but the goal is to allow pre-existing animations or
 * directives to create more complex animations that can be purely driven using CSS code.
 *
 * Note that only browsers that support CSS transitions and/or keyframe animations are capable of
 * rendering animations triggered via `$animateCss` (bad news for IE9 and lower).
 *
 * ## Usage
 * Once again, `$animateCss` is designed to be used inside of a registered JavaScript animation that
 * is powered by ngAnimate. It is possible to use `$animateCss` directly inside of a directive, however,
 * any automatic control over cancelling animations and/or preventing animations from being run on
 * child elements will not be handled by Angular. For this to work as expected, please use `$animate` to
 * trigger the animation and then setup a JavaScript animation that injects `$animateCss` to trigger
 * the CSS animation.
 *
 * The example below shows how we can create a folding animation on an element using `ng-if`:
 *
 * ```html
 * <!-- notice the `fold-animation` CSS class -->
 * <div ng-if="onOff" class="fold-animation">
 *   This element will go BOOM
 * </div>
 * <button ng-click="onOff=true">Fold In</button>
 * ```
 *
 * Now we create the **JavaScript animation** that will trigger the CSS transition:
 *
 * ```js
 * ngModule.animation('.fold-animation', ['$animateCss', function($animateCss) {
 *   return {
 *     enter: function(element, doneFn) {
 *       var height = element[0].offsetHeight;
 *       return $animateCss(element, {
 *         from: { height:'0px' },
 *         to: { height:height + 'px' },
 *         duration: 1 // one second
 *       });
 *     }
 *   }
 * }]);
 * ```
 *
 * ## More Advanced Uses
 *
 * `$animateCss` is the underlying code that ngAnimate uses to power **CSS-based animations** behind the scenes. Therefore CSS hooks
 * like `.ng-EVENT`, `.ng-EVENT-active`, `.ng-EVENT-stagger` are all features that can be triggered using `$animateCss` via JavaScript code.
 *
 * This also means that just about any combination of adding classes, removing classes, setting styles, dynamically setting a keyframe animation,
 * applying a hardcoded duration or delay value, changing the animation easing or applying a stagger animation are all options that work with
 * `$animateCss`. The service itself is smart enough to figure out the combination of options and examine the element styling properties in order
 * to provide a working animation that will run in CSS.
 *
 * The example below showcases a more advanced version of the `.fold-animation` from the example above:
 *
 * ```js
 * ngModule.animation('.fold-animation', ['$animateCss', function($animateCss) {
 *   return {
 *     enter: function(element, doneFn) {
 *       var height = element[0].offsetHeight;
 *       return $animateCss(element, {
 *         addClass: 'red large-text pulse-twice',
 *         easing: 'ease-out',
 *         from: { height:'0px' },
 *         to: { height:height + 'px' },
 *         duration: 1 // one second
 *       });
 *     }
 *   }
 * }]);
 * ```
 *
 * Since we're adding/removing CSS classes then the CSS transition will also pick those up:
 *
 * ```css
 * /&#42; since a hardcoded duration value of 1 was provided in the JavaScript animation code,
 * the CSS classes below will be transitioned despite them being defined as regular CSS classes &#42;/
 * .red { background:red; }
 * .large-text { font-size:20px; }
 *
 * /&#42; we can also use a keyframe animation and $animateCss will make it work alongside the transition &#42;/
 * .pulse-twice {
 *   animation: 0.5s pulse linear 2;
 *   -webkit-animation: 0.5s pulse linear 2;
 * }
 *
 * @keyframes pulse {
 *   from { transform: scale(0.5); }
 *   to { transform: scale(1.5); }
 * }
 *
 * @-webkit-keyframes pulse {
 *   from { -webkit-transform: scale(0.5); }
 *   to { -webkit-transform: scale(1.5); }
 * }
 * ```
 *
 * Given this complex combination of CSS classes, styles and options, `$animateCss` will figure everything out and make the animation happen.
 *
 * ## How the Options are handled
 *
 * `$animateCss` is very versatile and intelligent when it comes to figuring out what configurations to apply to the element to ensure the animation
 * works with the options provided. Say for example we were adding a class that contained a keyframe value and we wanted to also animate some inline
 * styles using the `from` and `to` properties.
 *
 * ```js
 * var animator = $animateCss(element, {
 *   from: { background:'red' },
 *   to: { background:'blue' }
 * });
 * animator.start();
 * ```
 *
 * ```css
 * .rotating-animation {
 *   animation:0.5s rotate linear;
 *   -webkit-animation:0.5s rotate linear;
 * }
 *
 * @keyframes rotate {
 *   from { transform: rotate(0deg); }
 *   to { transform: rotate(360deg); }
 * }
 *
 * @-webkit-keyframes rotate {
 *   from { -webkit-transform: rotate(0deg); }
 *   to { -webkit-transform: rotate(360deg); }
 * }
 * ```
 *
 * The missing pieces here are that we do not have a transition set (within the CSS code nor within the `$animateCss` options) and the duration of the animation is
 * going to be detected from what the keyframe styles on the CSS class are. In this event, `$animateCss` will automatically create an inline transition
 * style matching the duration detected from the keyframe style (which is present in the CSS class that is being added) and then prepare both the transition
 * and keyframe animations to run in parallel on the element. Then when the animation is underway the provided `from` and `to` CSS styles will be applied
 * and spread across the transition and keyframe animation.
 *
 * ## What is returned
 *
 * `$animateCss` works in two stages: a preparation phase and an animation phase. Therefore when `$animateCss` is first called it will NOT actually
 * start the animation. All that is going on here is that the element is being prepared for the animation (which means that the generated CSS classes are
 * added and removed on the element). Once `$animateCss` is called it will return an object with the following properties:
 *
 * ```js
 * var animator = $animateCss(element, { ... });
 * ```
 *
 * Now what do the contents of our `animator` variable look like:
 *
 * ```js
 * {
 *   // starts the animation
 *   start: Function,
 *
 *   // ends (aborts) the animation
 *   end: Function
 * }
 * ```
 *
 * To actually start the animation we need to run `animation.start()` which will then return a promise that we can hook into to detect when the animation ends.
 * If we choose not to run the animation then we MUST run `animation.end()` to perform a cleanup on the element (since some CSS classes and styles may have been
 * applied to the element during the preparation phase). Note that all other properties such as duration, delay, transitions and keyframes are just properties
 * and that changing them will not reconfigure the parameters of the animation.
 *
 * ### runner.done() vs runner.then()
 * It is documented that `animation.start()` will return a promise object and this is true, however, there is also an additional method available on the
 * runner called `.done(callbackFn)`. The done method works the same as `.finally(callbackFn)`, however, it does **not trigger a digest to occur**.
 * Therefore, for performance reasons, it's always best to use `runner.done(callback)` instead of `runner.then()`, `runner.catch()` or `runner.finally()`
 * unless you really need a digest to kick off afterwards.
 *
 * Keep in mind that, to make this easier, ngAnimate has tweaked the JS animations API to recognize when a runner instance is returned from $animateCss
 * (so there is no need to call `runner.done(doneFn)` inside of your JavaScript animation code).
 * Check the {@link ngAnimate.$animateCss#usage animation code above} to see how this works.
 *
 * @param {DOMElement} element the element that will be animated
 * @param {object} options the animation-related options that will be applied during the animation
 *
 * * `event` - The DOM event (e.g. enter, leave, move). When used, a generated CSS class of `ng-EVENT` and `ng-EVENT-active` will be applied
 * to the element during the animation. Multiple events can be provided when spaces are used as a separator. (Note that this will not perform any DOM operation.)
 * * `structural` - Indicates that the `ng-` prefix will be added to the event class. Setting to `false` or omitting will turn `ng-EVENT` and
 * `ng-EVENT-active` in `EVENT` and `EVENT-active`. Unused if `event` is omitted.
 * * `easing` - The CSS easing value that will be applied to the transition or keyframe animation (or both).
 * * `transitionStyle` - The raw CSS transition style that will be used (e.g. `1s linear all`).
 * * `keyframeStyle` - The raw CSS keyframe animation style that will be used (e.g. `1s my_animation linear`).
 * * `from` - The starting CSS styles (a key/value object) that will be applied at the start of the animation.
 * * `to` - The ending CSS styles (a key/value object) that will be applied across the animation via a CSS transition.
 * * `addClass` - A space separated list of CSS classes that will be added to the element and spread across the animation.
 * * `removeClass` - A space separated list of CSS classes that will be removed from the element and spread across the animation.
 * * `duration` - A number value representing the total duration of the transition and/or keyframe (note that a value of 1 is 1000ms). If a value of `0`
 * is provided then the animation will be skipped entirely.
 * * `delay` - A number value representing the total delay of the transition and/or keyframe (note that a value of 1 is 1000ms). If a value of `true` is
 * used then whatever delay value is detected from the CSS classes will be mirrored on the elements styles (e.g. by setting delay true then the style value
 * of the element will be `transition-delay: DETECTED_VALUE`). Using `true` is useful when you want the CSS classes and inline styles to all share the same
 * CSS delay value.
 * * `stagger` - A numeric time value representing the delay between successively animated elements
 * ({@link ngAnimate#css-staggering-animations Click here to learn how CSS-based staggering works in ngAnimate.})
 * * `staggerIndex` - The numeric index representing the stagger item (e.g. a value of 5 is equal to the sixth item in the stagger; therefore when a
 *   `stagger` option value of `0.1` is used then there will be a stagger delay of `600ms`)
 * * `applyClassesEarly` - Whether or not the classes being added or removed will be used when detecting the animation. This is set by `$animate` when enter/leave/move animations are fired to ensure that the CSS classes are resolved in time. (Note that this will prevent any transitions from occurring on the classes being added and removed.)
 * * `cleanupStyles` - Whether or not the provided `from` and `to` styles will be removed once
 *    the animation is closed. This is useful for when the styles are used purely for the sake of
 *    the animation and do not have a lasting visual effect on the element (e.g. a collapse and open animation).
 *    By default this value is set to `false`.
 *
 * @return {object} an object with start and end methods and details about the animation.
 *
 * * `start` - The method to start the animation. This will return a `Promise` when called.
 * * `end` - This method will cancel the animation and remove all applied CSS classes and styles.
 */
var ONE_SECOND = 1000;
var BASE_TEN = 10;

var ELAPSED_TIME_MAX_DECIMAL_PLACES = 3;
var CLOSING_TIME_BUFFER = 1.5;

var DETECT_CSS_PROPERTIES = {
  transitionDuration:      TRANSITION_DURATION_PROP,
  transitionDelay:         TRANSITION_DELAY_PROP,
  transitionProperty:      TRANSITION_PROP + PROPERTY_KEY,
  animationDuration:       ANIMATION_DURATION_PROP,
  animationDelay:          ANIMATION_DELAY_PROP,
  animationIterationCount: ANIMATION_PROP + ANIMATION_ITERATION_COUNT_KEY
};

var DETECT_STAGGER_CSS_PROPERTIES = {
  transitionDuration:      TRANSITION_DURATION_PROP,
  transitionDelay:         TRANSITION_DELAY_PROP,
  animationDuration:       ANIMATION_DURATION_PROP,
  animationDelay:          ANIMATION_DELAY_PROP
};

function getCssKeyframeDurationStyle(duration) {
  return [ANIMATION_DURATION_PROP, duration + 's'];
}

function getCssDelayStyle(delay, isKeyframeAnimation) {
  var prop = isKeyframeAnimation ? ANIMATION_DELAY_PROP : TRANSITION_DELAY_PROP;
  return [prop, delay + 's'];
}

function computeCssStyles($window, element, properties) {
  var styles = Object.create(null);
  var detectedStyles = $window.getComputedStyle(element) || {};
  forEach(properties, function(formalStyleName, actualStyleName) {
    var val = detectedStyles[formalStyleName];
    if (val) {
      var c = val.charAt(0);

      // only numerical-based values have a negative sign or digit as the first value
      if (c === '-' || c === '+' || c >= 0) {
        val = parseMaxTime(val);
      }

      // by setting this to null in the event that the delay is not set or is set directly as 0
      // then we can still allow for negative values to be used later on and not mistake this
      // value for being greater than any other negative value.
      if (val === 0) {
        val = null;
      }
      styles[actualStyleName] = val;
    }
  });

  return styles;
}

function parseMaxTime(str) {
  var maxValue = 0;
  var values = str.split(/\s*,\s*/);
  forEach(values, function(value) {
    // it's always safe to consider only second values and omit `ms` values since
    // getComputedStyle will always handle the conversion for us
    if (value.charAt(value.length - 1) == 's') {
      value = value.substring(0, value.length - 1);
    }
    value = parseFloat(value) || 0;
    maxValue = maxValue ? Math.max(value, maxValue) : value;
  });
  return maxValue;
}

function truthyTimingValue(val) {
  return val === 0 || val != null;
}

function getCssTransitionDurationStyle(duration, applyOnlyDuration) {
  var style = TRANSITION_PROP;
  var value = duration + 's';
  if (applyOnlyDuration) {
    style += DURATION_KEY;
  } else {
    value += ' linear all';
  }
  return [style, value];
}

function createLocalCacheLookup() {
  var cache = Object.create(null);
  return {
    flush: function() {
      cache = Object.create(null);
    },

    count: function(key) {
      var entry = cache[key];
      return entry ? entry.total : 0;
    },

    get: function(key) {
      var entry = cache[key];
      return entry && entry.value;
    },

    put: function(key, value) {
      if (!cache[key]) {
        cache[key] = { total: 1, value: value };
      } else {
        cache[key].total++;
      }
    }
  };
}

// we do not reassign an already present style value since
// if we detect the style property value again we may be
// detecting styles that were added via the `from` styles.
// We make use of `isDefined` here since an empty string
// or null value (which is what getPropertyValue will return
// for a non-existing style) will still be marked as a valid
// value for the style (a falsy value implies that the style
// is to be removed at the end of the animation). If we had a simple
// "OR" statement then it would not be enough to catch that.
function registerRestorableStyles(backup, node, properties) {
  forEach(properties, function(prop) {
    backup[prop] = isDefined(backup[prop])
        ? backup[prop]
        : node.style.getPropertyValue(prop);
  });
}

var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
  var gcsLookup = createLocalCacheLookup();
  var gcsStaggerLookup = createLocalCacheLookup();

  this.$get = ['$window', '$$jqLite', '$$AnimateRunner', '$timeout',
               '$$forceReflow', '$sniffer', '$$rAFScheduler', '$$animateQueue',
       function($window,   $$jqLite,   $$AnimateRunner,   $timeout,
                $$forceReflow,   $sniffer,   $$rAFScheduler, $$animateQueue) {

    var applyAnimationClasses = applyAnimationClassesFactory($$jqLite);

    var parentCounter = 0;
    function gcsHashFn(node, extraClasses) {
      var KEY = "$$ngAnimateParentKey";
      var parentNode = node.parentNode;
      var parentID = parentNode[KEY] || (parentNode[KEY] = ++parentCounter);
      return parentID + '-' + node.getAttribute('class') + '-' + extraClasses;
    }

    function computeCachedCssStyles(node, className, cacheKey, properties) {
      var timings = gcsLookup.get(cacheKey);

      if (!timings) {
        timings = computeCssStyles($window, node, properties);
        if (timings.animationIterationCount === 'infinite') {
          timings.animationIterationCount = 1;
        }
      }

      // we keep putting this in multiple times even though the value and the cacheKey are the same
      // because we're keeping an internal tally of how many duplicate animations are detected.
      gcsLookup.put(cacheKey, timings);
      return timings;
    }

    function computeCachedCssStaggerStyles(node, className, cacheKey, properties) {
      var stagger;

      // if we have one or more existing matches of matching elements
      // containing the same parent + CSS styles (which is how cacheKey works)
      // then staggering is possible
      if (gcsLookup.count(cacheKey) > 0) {
        stagger = gcsStaggerLookup.get(cacheKey);

        if (!stagger) {
          var staggerClassName = pendClasses(className, '-stagger');

          $$jqLite.addClass(node, staggerClassName);

          stagger = computeCssStyles($window, node, properties);

          // force the conversion of a null value to zero incase not set
          stagger.animationDuration = Math.max(stagger.animationDuration, 0);
          stagger.transitionDuration = Math.max(stagger.transitionDuration, 0);

          $$jqLite.removeClass(node, staggerClassName);

          gcsStaggerLookup.put(cacheKey, stagger);
        }
      }

      return stagger || {};
    }

    var cancelLastRAFRequest;
    var rafWaitQueue = [];
    function waitUntilQuiet(callback) {
      rafWaitQueue.push(callback);
      $$rAFScheduler.waitUntilQuiet(function() {
        gcsLookup.flush();
        gcsStaggerLookup.flush();

        // DO NOT REMOVE THIS LINE OR REFACTOR OUT THE `pageWidth` variable.
        // PLEASE EXAMINE THE `$$forceReflow` service to understand why.
        var pageWidth = $$forceReflow();

        // we use a for loop to ensure that if the queue is changed
        // during this looping then it will consider new requests
        for (var i = 0; i < rafWaitQueue.length; i++) {
          rafWaitQueue[i](pageWidth);
        }
        rafWaitQueue.length = 0;
      });
    }

    function computeTimings(node, className, cacheKey) {
      var timings = computeCachedCssStyles(node, className, cacheKey, DETECT_CSS_PROPERTIES);
      var aD = timings.animationDelay;
      var tD = timings.transitionDelay;
      timings.maxDelay = aD && tD
          ? Math.max(aD, tD)
          : (aD || tD);
      timings.maxDuration = Math.max(
          timings.animationDuration * timings.animationIterationCount,
          timings.transitionDuration);

      return timings;
    }

    return function init(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 = prepareAnimationOptions(copy(options));
      }

      var restoreStyles = {};
      var node = getDomNode(element);
      if (!node
          || !node.parentNode
          || !$$animateQueue.enabled()) {
        return closeAndReturnNoopAnimator();
      }

      var temporaryStyles = [];
      var classes = element.attr('class');
      var styles = packageStyles(options);
      var animationClosed;
      var animationPaused;
      var animationCompleted;
      var runner;
      var runnerHost;
      var maxDelay;
      var maxDelayTime;
      var maxDuration;
      var maxDurationTime;
      var startTime;
      var events = [];

      if (options.duration === 0 || (!$sniffer.animations && !$sniffer.transitions)) {
        return closeAndReturnNoopAnimator();
      }

      var method = options.event && isArray(options.event)
            ? options.event.join(' ')
            : options.event;

      var isStructural = method && options.structural;
      var structuralClassName = '';
      var addRemoveClassName = '';

      if (isStructural) {
        structuralClassName = pendClasses(method, EVENT_CLASS_PREFIX, true);
      } else if (method) {
        structuralClassName = method;
      }

      if (options.addClass) {
        addRemoveClassName += pendClasses(options.addClass, ADD_CLASS_SUFFIX);
      }

      if (options.removeClass) {
        if (addRemoveClassName.length) {
          addRemoveClassName += ' ';
        }
        addRemoveClassName += pendClasses(options.removeClass, REMOVE_CLASS_SUFFIX);
      }

      // there may be a situation where a structural animation is combined together
      // with CSS classes that need to resolve before the animation is computed.
      // However this means that there is no explicit CSS code to block the animation
      // from happening (by setting 0s none in the class name). If this is the case
      // we need to apply the classes before the first rAF so we know to continue if
      // there actually is a detected transition or keyframe animation
      if (options.applyClassesEarly && addRemoveClassName.length) {
        applyAnimationClasses(element, options);
      }

      var preparationClasses = [structuralClassName, addRemoveClassName].join(' ').trim();
      var fullClassName = classes + ' ' + preparationClasses;
      var activeClasses = pendClasses(preparationClasses, ACTIVE_CLASS_SUFFIX);
      var hasToStyles = styles.to && Object.keys(styles.to).length > 0;
      var containsKeyframeAnimation = (options.keyframeStyle || '').length > 0;

      // there is no way we can trigger an animation if no styles and
      // no classes are being applied which would then trigger a transition,
      // unless there a is raw keyframe value that is applied to the element.
      if (!containsKeyframeAnimation
           && !hasToStyles
           && !preparationClasses) {
        return closeAndReturnNoopAnimator();
      }

      var cacheKey, stagger;
      if (options.stagger > 0) {
        var staggerVal = parseFloat(options.stagger);
        stagger = {
          transitionDelay: staggerVal,
          animationDelay: staggerVal,
          transitionDuration: 0,
          animationDuration: 0
        };
      } else {
        cacheKey = gcsHashFn(node, fullClassName);
        stagger = computeCachedCssStaggerStyles(node, preparationClasses, cacheKey, DETECT_STAGGER_CSS_PROPERTIES);
      }

      if (!options.$$skipPreparationClasses) {
        $$jqLite.addClass(element, preparationClasses);
      }

      var applyOnlyDuration;

      if (options.transitionStyle) {
        var transitionStyle = [TRANSITION_PROP, options.transitionStyle];
        applyInlineStyle(node, transitionStyle);
        temporaryStyles.push(transitionStyle);
      }

      if (options.duration >= 0) {
        applyOnlyDuration = node.style[TRANSITION_PROP].length > 0;
        var durationStyle = getCssTransitionDurationStyle(options.duration, applyOnlyDuration);

        // we set the duration so that it will be picked up by getComputedStyle later
        applyInlineStyle(node, durationStyle);
        temporaryStyles.push(durationStyle);
      }

      if (options.keyframeStyle) {
        var keyframeStyle = [ANIMATION_PROP, options.keyframeStyle];
        applyInlineStyle(node, keyframeStyle);
        temporaryStyles.push(keyframeStyle);
      }

      var itemIndex = stagger
          ? options.staggerIndex >= 0
              ? options.staggerIndex
              : gcsLookup.count(cacheKey)
          : 0;

      var isFirst = itemIndex === 0;

      // this is a pre-emptive way of forcing the setup classes to be added and applied INSTANTLY
      // without causing any combination of transitions to kick in. By adding a negative delay value
      // it forces the setup class' transition to end immediately. We later then remove the negative
      // transition delay to allow for the transition to naturally do it's thing. The beauty here is
      // that if there is no transition defined then nothing will happen and this will also allow
      // other transitions to be stacked on top of each other without any chopping them out.
      if (isFirst && !options.skipBlocking) {
        blockTransitions(node, SAFE_FAST_FORWARD_DURATION_VALUE);
      }

      var timings = computeTimings(node, fullClassName, cacheKey);
      var relativeDelay = timings.maxDelay;
      maxDelay = Math.max(relativeDelay, 0);
      maxDuration = timings.maxDuration;

      var flags = {};
      flags.hasTransitions          = timings.transitionDuration > 0;
      flags.hasAnimations           = timings.animationDuration > 0;
      flags.hasTransitionAll        = flags.hasTransitions && timings.transitionProperty == 'all';
      flags.applyTransitionDuration = hasToStyles && (
                                        (flags.hasTransitions && !flags.hasTransitionAll)
                                         || (flags.hasAnimations && !flags.hasTransitions));
      flags.applyAnimationDuration  = options.duration && flags.hasAnimations;
      flags.applyTransitionDelay    = truthyTimingValue(options.delay) && (flags.applyTransitionDuration || flags.hasTransitions);
      flags.applyAnimationDelay     = truthyTimingValue(options.delay) && flags.hasAnimations;
      flags.recalculateTimingStyles = addRemoveClassName.length > 0;

      if (flags.applyTransitionDuration || flags.applyAnimationDuration) {
        maxDuration = options.duration ? parseFloat(options.duration) : maxDuration;

        if (flags.applyTransitionDuration) {
          flags.hasTransitions = true;
          timings.transitionDuration = maxDuration;
          applyOnlyDuration = node.style[TRANSITION_PROP + PROPERTY_KEY].length > 0;
          temporaryStyles.push(getCssTransitionDurationStyle(maxDuration, applyOnlyDuration));
        }

        if (flags.applyAnimationDuration) {
          flags.hasAnimations = true;
          timings.animationDuration = maxDuration;
          temporaryStyles.push(getCssKeyframeDurationStyle(maxDuration));
        }
      }

      if (maxDuration === 0 && !flags.recalculateTimingStyles) {
        return closeAndReturnNoopAnimator();
      }

      if (options.delay != null) {
        var delayStyle;
        if (typeof options.delay !== "boolean") {
          delayStyle = parseFloat(options.delay);
          // number in options.delay means we have to recalculate the delay for the closing timeout
          maxDelay = Math.max(delayStyle, 0);
        }

        if (flags.applyTransitionDelay) {
          temporaryStyles.push(getCssDelayStyle(delayStyle));
        }

        if (flags.applyAnimationDelay) {
          temporaryStyles.push(getCssDelayStyle(delayStyle, true));
        }
      }

      // we need to recalculate the delay value since we used a pre-emptive negative
      // delay value and the delay value is required for the final event checking. This
      // property will ensure that this will happen after the RAF phase has passed.
      if (options.duration == null && timings.transitionDuration > 0) {
        flags.recalculateTimingStyles = flags.recalculateTimingStyles || isFirst;
      }

      maxDelayTime = maxDelay * ONE_SECOND;
      maxDurationTime = maxDuration * ONE_SECOND;
      if (!options.skipBlocking) {
        flags.blockTransition = timings.transitionDuration > 0;
        flags.blockKeyframeAnimation = timings.animationDuration > 0 &&
                                       stagger.animationDelay > 0 &&
                                       stagger.animationDuration === 0;
      }

      if (options.from) {
        if (options.cleanupStyles) {
          registerRestorableStyles(restoreStyles, node, Object.keys(options.from));
        }
        applyAnimationFromStyles(element, options);
      }

      if (flags.blockTransition || flags.blockKeyframeAnimation) {
        applyBlocking(maxDuration);
      } else if (!options.skipBlocking) {
        blockTransitions(node, false);
      }

      // TODO(matsko): for 1.5 change this code to have an animator object for better debugging
      return {
        $$willAnimate: true,
        end: endFn,
        start: function() {
          if (animationClosed) return;

          runnerHost = {
            end: endFn,
            cancel: cancelFn,
            resume: null, //this will be set during the start() phase
            pause: null
          };

          runner = new $$AnimateRunner(runnerHost);

          waitUntilQuiet(start);

          // we don't have access to pause/resume the animation
          // since it hasn't run yet. AnimateRunner will therefore
          // set noop functions for resume and pause and they will
          // later be overridden once the animation is triggered
          return runner;
        }
      };

      function endFn() {
        close();
      }

      function cancelFn() {
        close(true);
      }

      function close(rejected) { // jshint ignore:line
        // if the promise has been called already then we shouldn't close
        // the animation again
        if (animationClosed || (animationCompleted && animationPaused)) return;
        animationClosed = true;
        animationPaused = false;

        if (!options.$$skipPreparationClasses) {
          $$jqLite.removeClass(element, preparationClasses);
        }
        $$jqLite.removeClass(element, activeClasses);

        blockKeyframeAnimations(node, false);
        blockTransitions(node, false);

        forEach(temporaryStyles, function(entry) {
          // There is only one way to remove inline style properties entirely from elements.
          // By using `removeProperty` this works, but we need to convert camel-cased CSS
          // styles down to hyphenated values.
          node.style[entry[0]] = '';
        });

        applyAnimationClasses(element, options);
        applyAnimationStyles(element, options);

        if (Object.keys(restoreStyles).length) {
          forEach(restoreStyles, function(value, prop) {
            value ? node.style.setProperty(prop, value)
                  : node.style.removeProperty(prop);
          });
        }

        // the reason why we have this option is to allow a synchronous closing callback
        // that is fired as SOON as the animation ends (when the CSS is removed) or if
        // the animation never takes off at all. A good example is a leave animation since
        // the element must be removed just after the animation is over or else the element
        // will appear on screen for one animation frame causing an overbearing flicker.
        if (options.onDone) {
          options.onDone();
        }

        if (events && events.length) {
          // Remove the transitionend / animationend listener(s)
          element.off(events.join(' '), onAnimationProgress);
        }

        //Cancel the fallback closing timeout and remove the timer data
        var animationTimerData = element.data(ANIMATE_TIMER_KEY);
        if (animationTimerData) {
          $timeout.cancel(animationTimerData[0].timer);
          element.removeData(ANIMATE_TIMER_KEY);
        }

        // if the preparation function fails then the promise is not setup
        if (runner) {
          runner.complete(!rejected);
        }
      }

      function applyBlocking(duration) {
        if (flags.blockTransition) {
          blockTransitions(node, duration);
        }

        if (flags.blockKeyframeAnimation) {
          blockKeyframeAnimations(node, !!duration);
        }
      }

      function closeAndReturnNoopAnimator() {
        runner = new $$AnimateRunner({
          end: endFn,
          cancel: cancelFn
        });

        // should flush the cache animation
        waitUntilQuiet(noop);
        close();

        return {
          $$willAnimate: false,
          start: function() {
            return runner;
          },
          end: endFn
        };
      }

      function onAnimationProgress(event) {
        event.stopPropagation();
        var ev = event.originalEvent || event;

        // we now always use `Date.now()` due to the recent changes with
        // event.timeStamp in Firefox, Webkit and Chrome (see #13494 for more info)
        var timeStamp = ev.$manualTimeStamp || Date.now();

        /* Firefox (or possibly just Gecko) likes to not round values up
         * when a ms measurement is used for the animation */
        var elapsedTime = parseFloat(ev.elapsedTime.toFixed(ELAPSED_TIME_MAX_DECIMAL_PLACES));

        /* $manualTimeStamp is a mocked timeStamp value which is set
         * within browserTrigger(). This is only here so that tests can
         * mock animations properly. Real events fallback to event.timeStamp,
         * or, if they don't, then a timeStamp is automatically created for them.
         * We're checking to see if the timeStamp surpasses the expected delay,
         * but we're using elapsedTime instead of the timeStamp on the 2nd
         * pre-condition since animationPauseds sometimes close off early */
        if (Math.max(timeStamp - startTime, 0) >= maxDelayTime && elapsedTime >= maxDuration) {
          // we set this flag to ensure that if the transition is paused then, when resumed,
          // the animation will automatically close itself since transitions cannot be paused.
          animationCompleted = true;
          close();
        }
      }

      function start() {
        if (animationClosed) return;
        if (!node.parentNode) {
          close();
          return;
        }

        // even though we only pause keyframe animations here the pause flag
        // will still happen when transitions are used. Only the transition will
        // not be paused since that is not possible. If the animation ends when
        // paused then it will not complete until unpaused or cancelled.
        var playPause = function(playAnimation) {
          if (!animationCompleted) {
            animationPaused = !playAnimation;
            if (timings.animationDuration) {
              var value = blockKeyframeAnimations(node, animationPaused);
              animationPaused
                  ? temporaryStyles.push(value)
                  : removeFromArray(temporaryStyles, value);
            }
          } else if (animationPaused && playAnimation) {
            animationPaused = false;
            close();
          }
        };

        // checking the stagger duration prevents an accidentally cascade of the CSS delay style
        // being inherited from the parent. If the transition duration is zero then we can safely
        // rely that the delay value is an intentional stagger delay style.
        var maxStagger = itemIndex > 0
                         && ((timings.transitionDuration && stagger.transitionDuration === 0) ||
                            (timings.animationDuration && stagger.animationDuration === 0))
                         && Math.max(stagger.animationDelay, stagger.transitionDelay);
        if (maxStagger) {
          $timeout(triggerAnimationStart,
                   Math.floor(maxStagger * itemIndex * ONE_SECOND),
                   false);
        } else {
          triggerAnimationStart();
        }

        // this will decorate the existing promise runner with pause/resume methods
        runnerHost.resume = function() {
          playPause(true);
        };

        runnerHost.pause = function() {
          playPause(false);
        };

        function triggerAnimationStart() {
          // just incase a stagger animation kicks in when the animation
          // itself was cancelled entirely
          if (animationClosed) return;

          applyBlocking(false);

          forEach(temporaryStyles, function(entry) {
            var key = entry[0];
            var value = entry[1];
            node.style[key] = value;
          });

          applyAnimationClasses(element, options);
          $$jqLite.addClass(element, activeClasses);

          if (flags.recalculateTimingStyles) {
            fullClassName = node.className + ' ' + preparationClasses;
            cacheKey = gcsHashFn(node, fullClassName);

            timings = computeTimings(node, fullClassName, cacheKey);
            relativeDelay = timings.maxDelay;
            maxDelay = Math.max(relativeDelay, 0);
            maxDuration = timings.maxDuration;

            if (maxDuration === 0) {
              close();
              return;
            }

            flags.hasTransitions = timings.transitionDuration > 0;
            flags.hasAnimations = timings.animationDuration > 0;
          }

          if (flags.applyAnimationDelay) {
            relativeDelay = typeof options.delay !== "boolean" && truthyTimingValue(options.delay)
                  ? parseFloat(options.delay)
                  : relativeDelay;

            maxDelay = Math.max(relativeDelay, 0);
            timings.animationDelay = relativeDelay;
            delayStyle = getCssDelayStyle(relativeDelay, true);
            temporaryStyles.push(delayStyle);
            node.style[delayStyle[0]] = delayStyle[1];
          }

          maxDelayTime = maxDelay * ONE_SECOND;
          maxDurationTime = maxDuration * ONE_SECOND;

          if (options.easing) {
            var easeProp, easeVal = options.easing;
            if (flags.hasTransitions) {
              easeProp = TRANSITION_PROP + TIMING_KEY;
              temporaryStyles.push([easeProp, easeVal]);
              node.style[easeProp] = easeVal;
            }
            if (flags.hasAnimations) {
              easeProp = ANIMATION_PROP + TIMING_KEY;
              temporaryStyles.push([easeProp, easeVal]);
              node.style[easeProp] = easeVal;
            }
          }

          if (timings.transitionDuration) {
            events.push(TRANSITIONEND_EVENT);
          }

          if (timings.animationDuration) {
            events.push(ANIMATIONEND_EVENT);
          }

          startTime = Date.now();
          var timerTime = maxDelayTime + CLOSING_TIME_BUFFER * maxDurationTime;
          var endTime = startTime + timerTime;

          var animationsData = element.data(ANIMATE_TIMER_KEY) || [];
          var setupFallbackTimer = true;
          if (animationsData.length) {
            var currentTimerData = animationsData[0];
            setupFallbackTimer = endTime > currentTimerData.expectedEndTime;
            if (setupFallbackTimer) {
              $timeout.cancel(currentTimerData.timer);
            } else {
              animationsData.push(close);
            }
          }

          if (setupFallbackTimer) {
            var timer = $timeout(onAnimationExpired, timerTime, false);
            animationsData[0] = {
              timer: timer,
              expectedEndTime: endTime
            };
            animationsData.push(close);
            element.data(ANIMATE_TIMER_KEY, animationsData);
          }

          if (events.length) {
            element.on(events.join(' '), onAnimationProgress);
          }

          if (options.to) {
            if (options.cleanupStyles) {
              registerRestorableStyles(restoreStyles, node, Object.keys(options.to));
            }
            applyAnimationToStyles(element, options);
          }
        }

        function onAnimationExpired() {
          var animationsData = element.data(ANIMATE_TIMER_KEY);

          // this will be false in the event that the element was
          // removed from the DOM (via a leave animation or something
          // similar)
          if (animationsData) {
            for (var i = 1; i < animationsData.length; i++) {
              animationsData[i]();
            }
            element.removeData(ANIMATE_TIMER_KEY);
          }
        }
      }
    };
  }];
}];

var $$AnimateCssDriverProvider = ['$$animationProvider', function($$animationProvider) {
  $$animationProvider.drivers.push('$$animateCssDriver');

  var NG_ANIMATE_SHIM_CLASS_NAME = 'ng-animate-shim';
  var NG_ANIMATE_ANCHOR_CLASS_NAME = 'ng-anchor';

  var NG_OUT_ANCHOR_CLASS_NAME = 'ng-anchor-out';
  var NG_IN_ANCHOR_CLASS_NAME = 'ng-anchor-in';

  function isDocumentFragment(node) {
    return node.parentNode && node.parentNode.nodeType === 11;
  }

  this.$get = ['$animateCss', '$rootScope', '$$AnimateRunner', '$rootElement', '$sniffer', '$$jqLite', '$document',
       function($animateCss,   $rootScope,   $$AnimateRunner,   $rootElement,   $sniffer,   $$jqLite,   $document) {

    // only browsers that support these properties can render animations
    if (!$sniffer.animations && !$sniffer.transitions) return noop;

    var bodyNode = $document[0].body;
    var rootNode = getDomNode($rootElement);

    var rootBodyElement = jqLite(
      // this is to avoid using something that exists outside of the body
      // we also special case the doc fragment case because our unit test code
      // appends the $rootElement to the body after the app has been bootstrapped
      isDocumentFragment(rootNode) || bodyNode.contains(rootNode) ? rootNode : bodyNode
    );

    var applyAnimationClasses = applyAnimationClassesFactory($$jqLite);

    return function initDriverFn(animationDetails) {
      return animationDetails.from && animationDetails.to
          ? prepareFromToAnchorAnimation(animationDetails.from,
                                         animationDetails.to,
                                         animationDetails.classes,
                                         animationDetails.anchors)
          : prepareRegularAnimation(animationDetails);
    };

    function filterCssClasses(classes) {
      //remove all the `ng-` stuff
      return classes.replace(/\bng-\S+\b/g, '');
    }

    function getUniqueValues(a, b) {
      if (isString(a)) a = a.split(' ');
      if (isString(b)) b = b.split(' ');
      return a.filter(function(val) {
        return b.indexOf(val) === -1;
      }).join(' ');
    }

    function prepareAnchoredAnimation(classes, outAnchor, inAnchor) {
      var clone = jqLite(getDomNode(outAnchor).cloneNode(true));
      var startingClasses = filterCssClasses(getClassVal(clone));

      outAnchor.addClass(NG_ANIMATE_SHIM_CLASS_NAME);
      inAnchor.addClass(NG_ANIMATE_SHIM_CLASS_NAME);

      clone.addClass(NG_ANIMATE_ANCHOR_CLASS_NAME);

      rootBodyElement.append(clone);

      var animatorIn, animatorOut = prepareOutAnimation();

      // the user may not end up using the `out` animation and
      // only making use of the `in` animation or vice-versa.
      // In either case we should allow this and not assume the
      // animation is over unless both animations are not used.
      if (!animatorOut) {
        animatorIn = prepareInAnimation();
        if (!animatorIn) {
          return end();
        }
      }

      var startingAnimator = animatorOut || animatorIn;

      return {
        start: function() {
          var runner;

          var currentAnimation = startingAnimator.start();
          currentAnimation.done(function() {
            currentAnimation = null;
            if (!animatorIn) {
              animatorIn = prepareInAnimation();
              if (animatorIn) {
                currentAnimation = animatorIn.start();
                currentAnimation.done(function() {
                  currentAnimation = null;
                  end();
                  runner.complete();
                });
                return currentAnimation;
              }
            }
            // in the event that there is no `in` animation
            end();
            runner.complete();
          });

          runner = new $$AnimateRunner({
            end: endFn,
            cancel: endFn
          });

          return runner;

          function endFn() {
            if (currentAnimation) {
              currentAnimation.end();
            }
          }
        }
      };

      function calculateAnchorStyles(anchor) {
        var styles = {};

        var coords = getDomNode(anchor).getBoundingClientRect();

        // we iterate directly since safari messes up and doesn't return
        // all the keys for the coords object when iterated
        forEach(['width','height','top','left'], function(key) {
          var value = coords[key];
          switch (key) {
            case 'top':
              value += bodyNode.scrollTop;
              break;
            case 'left':
              value += bodyNode.scrollLeft;
              break;
          }
          styles[key] = Math.floor(value) + 'px';
        });
        return styles;
      }

      function prepareOutAnimation() {
        var animator = $animateCss(clone, {
          addClass: NG_OUT_ANCHOR_CLASS_NAME,
          delay: true,
          from: calculateAnchorStyles(outAnchor)
        });

        // read the comment within `prepareRegularAnimation` to understand
        // why this check is necessary
        return animator.$$willAnimate ? animator : null;
      }

      function getClassVal(element) {
        return element.attr('class') || '';
      }

      function prepareInAnimation() {
        var endingClasses = filterCssClasses(getClassVal(inAnchor));
        var toAdd = getUniqueValues(endingClasses, startingClasses);
        var toRemove = getUniqueValues(startingClasses, endingClasses);

        var animator = $animateCss(clone, {
          to: calculateAnchorStyles(inAnchor),
          addClass: NG_IN_ANCHOR_CLASS_NAME + ' ' + toAdd,
          removeClass: NG_OUT_ANCHOR_CLASS_NAME + ' ' + toRemove,
          delay: true
        });

        // read the comment within `prepareRegularAnimation` to understand
        // why this check is necessary
        return animator.$$willAnimate ? animator : null;
      }

      function end() {
        clone.remove();
        outAnchor.removeClass(NG_ANIMATE_SHIM_CLASS_NAME);
        inAnchor.removeClass(NG_ANIMATE_SHIM_CLASS_NAME);
      }
    }

    function prepareFromToAnchorAnimation(from, to, classes, anchors) {
      var fromAnimation = prepareRegularAnimation(from, noop);
      var toAnimation = prepareRegularAnimation(to, noop);

      var anchorAnimations = [];
      forEach(anchors, function(anchor) {
        var outElement = anchor['out'];
        var inElement = anchor['in'];
        var animator = prepareAnchoredAnimation(classes, outElement, inElement);
        if (animator) {
          anchorAnimations.push(animator);
        }
      });

      // no point in doing anything when there are no elements to animate
      if (!fromAnimation && !toAnimation && anchorAnimations.length === 0) return;

      return {
        start: function() {
          var animationRunners = [];

          if (fromAnimation) {
            animationRunners.push(fromAnimation.start());
          }

          if (toAnimation) {
            animationRunners.push(toAnimation.start());
          }

          forEach(anchorAnimations, function(animation) {
            animationRunners.push(animation.start());
          });

          var runner = new $$AnimateRunner({
            end: endFn,
            cancel: endFn // CSS-driven animations cannot be cancelled, only ended
          });

          $$AnimateRunner.all(animationRunners, function(status) {
            runner.complete(status);
          });

          return runner;

          function endFn() {
            forEach(animationRunners, function(runner) {
              runner.end();
            });
          }
        }
      };
    }

    function prepareRegularAnimation(animationDetails) {
      var element = animationDetails.element;
      var options = animationDetails.options || {};

      if (animationDetails.structural) {
        options.event = animationDetails.event;
        options.structural = true;
        options.applyClassesEarly = true;

        // we special case the leave animation since we want to ensure that
        // the element is removed as soon as the animation is over. Otherwise
        // a flicker might appear or the element may not be removed at all
        if (animationDetails.event === 'leave') {
          options.onDone = options.domOperation;
        }
      }

      // We assign the preparationClasses as the actual animation event since
      // the internals of $animateCss will just suffix the event token values
      // with `-active` to trigger the animation.
      if (options.preparationClasses) {
        options.event = concatWithSpace(options.event, options.preparationClasses);
      }

      var animator = $animateCss(element, options);

      // the driver lookup code inside of $$animation attempts to spawn a
      // driver one by one until a driver returns a.$$willAnimate animator object.
      // $animateCss will always return an object, however, it will pass in
      // a flag as a hint as to whether an animation was detected or not
      return animator.$$willAnimate ? animator : null;
    }
  }];
}];

// TODO(matsko): use caching here to speed things up for detection
// TODO(matsko): add documentation
//  by the time...

var $$AnimateJsProvider = ['$animateProvider', function($animateProvider) {
  this.$get = ['$injector', '$$AnimateRunner', '$$jqLite',
       function($injector,   $$AnimateRunner,   $$jqLite) {

    var applyAnimationClasses = applyAnimationClassesFactory($$jqLite);
         // $animateJs(element, 'enter');
    return function(element, event, classes, options) {
      var animationClosed = false;

      // the `classes` argument is optional and if it is not used
      // then the classes will be resolved from the element's className
      // property as well as options.addClass/options.removeClass.
      if (arguments.length === 3 && isObject(classes)) {
        options = classes;
        classes = null;
      }

      options = prepareAnimationOptions(options);
      if (!classes) {
        classes = element.attr('class') || '';
        if (options.addClass) {
          classes += ' ' + options.addClass;
        }
        if (options.removeClass) {
          classes += ' ' + options.removeClass;
        }
      }

      var classesToAdd = options.addClass;
      var classesToRemove = options.removeClass;

      // the lookupAnimations function returns a series of animation objects that are
      // matched up with one or more of the CSS classes. These animation objects are
      // defined via the module.animation factory function. If nothing is detected then
      // we don't return anything which then makes $animation query the next driver.
      var animations = lookupAnimations(classes);
      var before, after;
      if (animations.length) {
        var afterFn, beforeFn;
        if (event == 'leave') {
          beforeFn = 'leave';
          afterFn = 'afterLeave'; // TODO(matsko): get rid of this
        } else {
          beforeFn = 'before' + event.charAt(0).toUpperCase() + event.substr(1);
          afterFn = event;
        }

        if (event !== 'enter' && event !== 'move') {
          before = packageAnimations(element, event, options, animations, beforeFn);
        }
        after  = packageAnimations(element, event, options, animations, afterFn);
      }

      // no matching animations
      if (!before && !after) return;

      function applyOptions() {
        options.domOperation();
        applyAnimationClasses(element, options);
      }

      function close() {
        animationClosed = true;
        applyOptions();
        applyAnimationStyles(element, options);
      }

      var runner;

      return {
        $$willAnimate: true,
        end: function() {
          if (runner) {
            runner.end();
          } else {
            close();
            runner = new $$AnimateRunner();
            runner.complete(true);
          }
          return runner;
        },
        start: function() {
          if (runner) {
            return runner;
          }

          runner = new $$AnimateRunner();
          var closeActiveAnimations;
          var chain = [];

          if (before) {
            chain.push(function(fn) {
              closeActiveAnimations = before(fn);
            });
          }

          if (chain.length) {
            chain.push(function(fn) {
              applyOptions();
              fn(true);
            });
          } else {
            applyOptions();
          }

          if (after) {
            chain.push(function(fn) {
              closeActiveAnimations = after(fn);
            });
          }

          runner.setHost({
            end: function() {
              endAnimations();
            },
            cancel: function() {
              endAnimations(true);
            }
          });

          $$AnimateRunner.chain(chain, onComplete);
          return runner;

          function onComplete(success) {
            close(success);
            runner.complete(success);
          }

          function endAnimations(cancelled) {
            if (!animationClosed) {
              (closeActiveAnimations || noop)(cancelled);
              onComplete(cancelled);
            }
          }
        }
      };

      function executeAnimationFn(fn, element, event, options, onDone) {
        var args;
        switch (event) {
          case 'animate':
            args = [element, options.from, options.to, onDone];
            break;

          case 'setClass':
            args = [element, classesToAdd, classesToRemove, onDone];
            break;

          case 'addClass':
            args = [element, classesToAdd, onDone];
            break;

          case 'removeClass':
            args = [element, classesToRemove, onDone];
            break;

          default:
            args = [element, onDone];
            break;
        }

        args.push(options);

        var value = fn.apply(fn, args);
        if (value) {
          if (isFunction(value.start)) {
            value = value.start();
          }

          if (value instanceof $$AnimateRunner) {
            value.done(onDone);
          } else if (isFunction(value)) {
            // optional onEnd / onCancel callback
            return value;
          }
        }

        return noop;
      }

      function groupEventedAnimations(element, event, options, animations, fnName) {
        var operations = [];
        forEach(animations, function(ani) {
          var animation = ani[fnName];
          if (!animation) return;

          // note that all of these animations will run in parallel
          operations.push(function() {
            var runner;
            var endProgressCb;

            var resolved = false;
            var onAnimationComplete = function(rejected) {
              if (!resolved) {
                resolved = true;
                (endProgressCb || noop)(rejected);
                runner.complete(!rejected);
              }
            };

            runner = new $$AnimateRunner({
              end: function() {
                onAnimationComplete();
              },
              cancel: function() {
                onAnimationComplete(true);
              }
            });

            endProgressCb = executeAnimationFn(animation, element, event, options, function(result) {
              var cancelled = result === false;
              onAnimationComplete(cancelled);
            });

            return runner;
          });
        });

        return operations;
      }

      function packageAnimations(element, event, options, animations, fnName) {
        var operations = groupEventedAnimations(element, event, options, animations, fnName);
        if (operations.length === 0) {
          var a,b;
          if (fnName === 'beforeSetClass') {
            a = groupEventedAnimations(element, 'removeClass', options, animations, 'beforeRemoveClass');
            b = groupEventedAnimations(element, 'addClass', options, animations, 'beforeAddClass');
          } else if (fnName === 'setClass') {
            a = groupEventedAnimations(element, 'removeClass', options, animations, 'removeClass');
            b = groupEventedAnimations(element, 'addClass', options, animations, 'addClass');
          }

          if (a) {
            operations = operations.concat(a);
          }
          if (b) {
            operations = operations.concat(b);
          }
        }

        if (operations.length === 0) return;

        // TODO(matsko): add documentation
        return function startAnimation(callback) {
          var runners = [];
          if (operations.length) {
            forEach(operations, function(animateFn) {
              runners.push(animateFn());
            });
          }

          runners.length ? $$AnimateRunner.all(runners, callback) : callback();

          return function endFn(reject) {
            forEach(runners, function(runner) {
              reject ? runner.cancel() : runner.end();
            });
          };
        };
      }
    };

    function lookupAnimations(classes) {
      classes = isArray(classes) ? classes : classes.split(' ');
      var matches = [], flagMap = {};
      for (var i=0; i < classes.length; i++) {
        var klass = classes[i],
            animationFactory = $animateProvider.$$registeredAnimations[klass];
        if (animationFactory && !flagMap[klass]) {
          matches.push($injector.get(animationFactory));
          flagMap[klass] = true;
        }
      }
      return matches;
    }
  }];
}];

var $$AnimateJsDriverProvider = ['$$animationProvider', function($$animationProvider) {
  $$animationProvider.drivers.push('$$animateJsDriver');
  this.$get = ['$$animateJs', '$$AnimateRunner', function($$animateJs, $$AnimateRunner) {
    return function initDriverFn(animationDetails) {
      if (animationDetails.from && animationDetails.to) {
        var fromAnimation = prepareAnimation(animationDetails.from);
        var toAnimation = prepareAnimation(animationDetails.to);
        if (!fromAnimation && !toAnimation) return;

        return {
          start: function() {
            var animationRunners = [];

            if (fromAnimation) {
              animationRunners.push(fromAnimation.start());
            }

            if (toAnimation) {
              animationRunners.push(toAnimation.start());
            }

            $$AnimateRunner.all(animationRunners, done);

            var runner = new $$AnimateRunner({
              end: endFnFactory(),
              cancel: endFnFactory()
            });

            return runner;

            function endFnFactory() {
              return function() {
                forEach(animationRunners, function(runner) {
                  // at this point we cannot cancel animations for groups just yet. 1.5+
                  runner.end();
                });
              };
            }

            function done(status) {
              runner.complete(status);
            }
          }
        };
      } else {
        return prepareAnimation(animationDetails);
      }
    };

    function prepareAnimation(animationDetails) {
      // TODO(matsko): make sure to check for grouped animations and delegate down to normal animations
      var element = animationDetails.element;
      var event = animationDetails.event;
      var options = animationDetails.options;
      var classes = animationDetails.classes;
      return $$animateJs(element, event, classes, options);
    }
  }];
}];

var NG_ANIMATE_ATTR_NAME = 'data-ng-animate';
var NG_ANIMATE_PIN_DATA = '$ngAnimatePin';
var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
  var PRE_DIGEST_STATE = 1;
  var RUNNING_STATE = 2;
  var ONE_SPACE = ' ';

  var rules = this.rules = {
    skip: [],
    cancel: [],
    join: []
  };

  function makeTruthyCssClassMap(classString) {
    if (!classString) {
      return null;
    }

    var keys = classString.split(ONE_SPACE);
    var map = Object.create(null);

    forEach(keys, function(key) {
      map[key] = true;
    });
    return map;
  }

  function hasMatchingClasses(newClassString, currentClassString) {
    if (newClassString && currentClassString) {
      var currentClassMap = makeTruthyCssClassMap(currentClassString);
      return newClassString.split(ONE_SPACE).some(function(className) {
        return currentClassMap[className];
      });
    }
  }

  function isAllowed(ruleType, element, currentAnimation, previousAnimation) {
    return rules[ruleType].some(function(fn) {
      return fn(element, currentAnimation, previousAnimation);
    });
  }

  function hasAnimationClasses(animation, and) {
    var a = (animation.addClass || '').length > 0;
    var b = (animation.removeClass || '').length > 0;
    return and ? a && b : a || b;
  }

  rules.join.push(function(element, newAnimation, currentAnimation) {
    // if the new animation is class-based then we can just tack that on
    return !newAnimation.structural && hasAnimationClasses(newAnimation);
  });

  rules.skip.push(function(element, newAnimation, currentAnimation) {
    // there is no need to animate anything if no classes are being added and
    // there is no structural animation that will be triggered
    return !newAnimation.structural && !hasAnimationClasses(newAnimation);
  });

  rules.skip.push(function(element, newAnimation, currentAnimation) {
    // why should we trigger a new structural animation if the element will
    // be removed from the DOM anyway?
    return currentAnimation.event == 'leave' && newAnimation.structural;
  });

  rules.skip.push(function(element, newAnimation, currentAnimation) {
    // if there is an ongoing current animation then don't even bother running the class-based animation
    return currentAnimation.structural && currentAnimation.state === RUNNING_STATE && !newAnimation.structural;
  });

  rules.cancel.push(function(element, newAnimation, currentAnimation) {
    // there can never be two structural animations running at the same time
    return currentAnimation.structural && newAnimation.structural;
  });

  rules.cancel.push(function(element, newAnimation, currentAnimation) {
    // if the previous animation is already running, but the new animation will
    // be triggered, but the new animation is structural
    return currentAnimation.state === RUNNING_STATE && newAnimation.structural;
  });

  rules.cancel.push(function(element, newAnimation, currentAnimation) {
    // cancel the animation if classes added / removed in both animation cancel each other out,
    // but only if the current animation isn't structural

    if (currentAnimation.structural) return false;

    var nA = newAnimation.addClass;
    var nR = newAnimation.removeClass;
    var cA = currentAnimation.addClass;
    var cR = currentAnimation.removeClass;

    // early detection to save the global CPU shortage :)
    if ((isUndefined(nA) && isUndefined(nR)) || (isUndefined(cA) && isUndefined(cR))) {
      return false;
    }

    return hasMatchingClasses(nA, cR) || hasMatchingClasses(nR, cA);
  });

  this.$get = ['$$rAF', '$rootScope', '$rootElement', '$document', '$$HashMap',
               '$$animation', '$$AnimateRunner', '$templateRequest', '$$jqLite', '$$forceReflow',
       function($$rAF,   $rootScope,   $rootElement,   $document,   $$HashMap,
                $$animation,   $$AnimateRunner,   $templateRequest,   $$jqLite,   $$forceReflow) {

    var activeAnimationsLookup = new $$HashMap();
    var disabledElementsLookup = new $$HashMap();
    var animationsEnabled = null;

    function postDigestTaskFactory() {
      var postDigestCalled = false;
      return function(fn) {
        // we only issue a call to postDigest before
        // it has first passed. This prevents any callbacks
        // from not firing once the animation has completed
        // since it will be out of the digest cycle.
        if (postDigestCalled) {
          fn();
        } else {
          $rootScope.$$postDigest(function() {
            postDigestCalled = true;
            fn();
          });
        }
      };
    }

    // Wait until all directive and route-related templates are downloaded and
    // compiled. The $templateRequest.totalPendingRequests variable keeps track of
    // all of the remote templates being currently downloaded. If there are no
    // templates currently downloading then the watcher will still fire anyway.
    var deregisterWatch = $rootScope.$watch(
      function() { return $templateRequest.totalPendingRequests === 0; },
      function(isEmpty) {
        if (!isEmpty) return;
        deregisterWatch();

        // Now that all templates have been downloaded, $animate will wait until
        // the post digest queue is empty before enabling animations. By having two
        // calls to $postDigest calls we can ensure that the flag is enabled at the
        // very end of the post digest queue. Since all of the animations in $animate
        // use $postDigest, it's important that the code below executes at the end.
        // This basically means that the page is fully downloaded and compiled before
        // any animations are triggered.
        $rootScope.$$postDigest(function() {
          $rootScope.$$postDigest(function() {
            // we check for null directly in the event that the application already called
            // .enabled() with whatever arguments that it provided it with
            if (animationsEnabled === null) {
              animationsEnabled = true;
            }
          });
        });
      }
    );

    var callbackRegistry = {};

    // remember that the classNameFilter is set during the provider/config
    // stage therefore we can optimize here and setup a helper function
    var classNameFilter = $animateProvider.classNameFilter();
    var isAnimatableClassName = !classNameFilter
              ? function() { return true; }
              : function(className) {
                return classNameFilter.test(className);
              };

    var applyAnimationClasses = applyAnimationClassesFactory($$jqLite);

    function normalizeAnimationDetails(element, animation) {
      return mergeAnimationDetails(element, animation, {});
    }

    // IE9-11 has no method "contains" in SVG element and in Node.prototype. Bug #10259.
    var contains = window.Node.prototype.contains || function(arg) {
      // jshint bitwise: false
      return this === arg || !!(this.compareDocumentPosition(arg) & 16);
      // jshint bitwise: true
    };

    function findCallbacks(parent, element, event) {
      var targetNode = getDomNode(element);
      var targetParentNode = getDomNode(parent);

      var matches = [];
      var entries = callbackRegistry[event];
      if (entries) {
        forEach(entries, function(entry) {
          if (contains.call(entry.node, targetNode)) {
            matches.push(entry.callback);
          } else if (event === 'leave' && contains.call(entry.node, targetParentNode)) {
            matches.push(entry.callback);
          }
        });
      }

      return matches;
    }

    function filterFromRegistry(list, matchContainer, matchCallback) {
      var containerNode = extractElementNode(matchContainer);
      return list.filter(function(entry) {
        var isMatch = entry.node === containerNode &&
                        (!matchCallback || entry.callback === matchCallback);
        return !isMatch;
      });
    }

    function cleanupEventListeners(phase, element) {
      if (phase === 'close' && !element[0].parentNode) {
        // If the element is not attached to a parentNode, it has been removed by
        // the domOperation, and we can safely remove the event callbacks
        $animate.off(element);
      }
    }

    var $animate = {
      on: function(event, container, callback) {
        var node = extractElementNode(container);
        callbackRegistry[event] = callbackRegistry[event] || [];
        callbackRegistry[event].push({
          node: node,
          callback: callback
        });

        // Remove the callback when the element is removed from the DOM
        jqLite(container).on('$destroy', function() {
          var animationDetails = activeAnimationsLookup.get(node);

          if (!animationDetails) {
            // If there's an animation ongoing, the callback calling code will remove
            // the event listeners. If we'd remove here, the callbacks would be removed
            // before the animation ends
            $animate.off(event, container, callback);
          }
        });
      },

      off: function(event, container, callback) {
        if (arguments.length === 1 && !angular.isString(arguments[0])) {
          container = arguments[0];
          for (var eventType in callbackRegistry) {
            callbackRegistry[eventType] = filterFromRegistry(callbackRegistry[eventType], container);
          }

          return;
        }

        var entries = callbackRegistry[event];
        if (!entries) return;

        callbackRegistry[event] = arguments.length === 1
            ? null
            : filterFromRegistry(entries, container, callback);
      },

      pin: function(element, parentElement) {
        assertArg(isElement(element), 'element', 'not an element');
        assertArg(isElement(parentElement), 'parentElement', 'not an element');
        element.data(NG_ANIMATE_PIN_DATA, parentElement);
      },

      push: function(element, event, options, domOperation) {
        options = options || {};
        options.domOperation = domOperation;
        return queueAnimation(element, event, options);
      },

      // this method has four signatures:
      //  () - global getter
      //  (bool) - global setter
      //  (element) - element getter
      //  (element, bool) - element setter<F37>
      enabled: function(element, bool) {
        var argCount = arguments.length;

        if (argCount === 0) {
          // () - Global getter
          bool = !!animationsEnabled;
        } else {
          var hasElement = isElement(element);

          if (!hasElement) {
            // (bool) - Global setter
            bool = animationsEnabled = !!element;
          } else {
            var node = getDomNode(element);
            var recordExists = disabledElementsLookup.get(node);

            if (argCount === 1) {
              // (element) - Element getter
              bool = !recordExists;
            } else {
              // (element, bool) - Element setter
              disabledElementsLookup.put(node, !bool);
            }
          }
        }

        return bool;
      }
    };

    return $animate;

    function queueAnimation(element, event, initialOptions) {
      // we always make a copy of the options since
      // there should never be any side effects on
      // the input data when running `$animateCss`.
      var options = copy(initialOptions);

      var node, parent;
      element = stripCommentsFromElement(element);
      if (element) {
        node = getDomNode(element);
        parent = element.parent();
      }

      options = prepareAnimationOptions(options);

      // we create a fake runner with a working promise.
      // These methods will become available after the digest has passed
      var runner = new $$AnimateRunner();

      // this is used to trigger callbacks in postDigest mode
      var runInNextPostDigestOrNow = postDigestTaskFactory();

      if (isArray(options.addClass)) {
        options.addClass = options.addClass.join(' ');
      }

      if (options.addClass && !isString(options.addClass)) {
        options.addClass = null;
      }

      if (isArray(options.removeClass)) {
        options.removeClass = options.removeClass.join(' ');
      }

      if (options.removeClass && !isString(options.removeClass)) {
        options.removeClass = null;
      }

      if (options.from && !isObject(options.from)) {
        options.from = null;
      }

      if (options.to && !isObject(options.to)) {
        options.to = null;
      }

      // there are situations where a directive issues an animation for
      // a jqLite wrapper that contains only comment nodes... If this
      // happens then there is no way we can perform an animation
      if (!node) {
        close();
        return runner;
      }

      var className = [node.className, options.addClass, options.removeClass].join(' ');
      if (!isAnimatableClassName(className)) {
        close();
        return runner;
      }

      var isStructural = ['enter', 'move', 'leave'].indexOf(event) >= 0;

      var documentHidden = $document[0].hidden;

      // this is a hard disable of all animations for the application or on
      // the element itself, therefore  there is no need to continue further
      // past this point if not enabled
      // Animations are also disabled if the document is currently hidden (page is not visible
      // to the user), because browsers slow down or do not flush calls to requestAnimationFrame
      var skipAnimations = !animationsEnabled || documentHidden || disabledElementsLookup.get(node);
      var existingAnimation = (!skipAnimations && activeAnimationsLookup.get(node)) || {};
      var hasExistingAnimation = !!existingAnimation.state;

      // there is no point in traversing the same collection of parent ancestors if a followup
      // animation will be run on the same element that already did all that checking work
      if (!skipAnimations && (!hasExistingAnimation || existingAnimation.state != PRE_DIGEST_STATE)) {
        skipAnimations = !areAnimationsAllowed(element, parent, event);
      }

      if (skipAnimations) {
        // Callbacks should fire even if the document is hidden (regression fix for issue #14120)
        if (documentHidden) notifyProgress(runner, event, 'start');
        close();
        if (documentHidden) notifyProgress(runner, event, 'close');
        return runner;
      }

      if (isStructural) {
        closeChildAnimations(element);
      }

      var newAnimation = {
        structural: isStructural,
        element: element,
        event: event,
        addClass: options.addClass,
        removeClass: options.removeClass,
        close: close,
        options: options,
        runner: runner
      };

      if (hasExistingAnimation) {
        var skipAnimationFlag = isAllowed('skip', element, newAnimation, existingAnimation);
        if (skipAnimationFlag) {
          if (existingAnimation.state === RUNNING_STATE) {
            close();
            return runner;
          } else {
            mergeAnimationDetails(element, existingAnimation, newAnimation);
            return existingAnimation.runner;
          }
        }
        var cancelAnimationFlag = isAllowed('cancel', element, newAnimation, existingAnimation);
        if (cancelAnimationFlag) {
          if (existingAnimation.state === RUNNING_STATE) {
            // this will end the animation right away and it is safe
            // to do so since the animation is already running and the
            // runner callback code will run in async
            existingAnimation.runner.end();
          } else if (existingAnimation.structural) {
            // this means that the animation is queued into a digest, but
            // hasn't started yet. Therefore it is safe to run the close
            // method which will call the runner methods in async.
            existingAnimation.close();
          } else {
            // this will merge the new animation options into existing animation options
            mergeAnimationDetails(element, existingAnimation, newAnimation);

            return existingAnimation.runner;
          }
        } else {
          // a joined animation means that this animation will take over the existing one
          // so an example would involve a leave animation taking over an enter. Then when
          // the postDigest kicks in the enter will be ignored.
          var joinAnimationFlag = isAllowed('join', element, newAnimation, existingAnimation);
          if (joinAnimationFlag) {
            if (existingAnimation.state === RUNNING_STATE) {
              normalizeAnimationDetails(element, newAnimation);
            } else {
              applyGeneratedPreparationClasses(element, isStructural ? event : null, options);

              event = newAnimation.event = existingAnimation.event;
              options = mergeAnimationDetails(element, existingAnimation, newAnimation);

              //we return the same runner since only the option values of this animation will
              //be fed into the `existingAnimation`.
              return existingAnimation.runner;
            }
          }
        }
      } else {
        // normalization in this case means that it removes redundant CSS classes that
        // already exist (addClass) or do not exist (removeClass) on the element
        normalizeAnimationDetails(element, newAnimation);
      }

      // when the options are merged and cleaned up we may end up not having to do
      // an animation at all, therefore we should check this before issuing a post
      // digest callback. Structural animations will always run no matter what.
      var isValidAnimation = newAnimation.structural;
      if (!isValidAnimation) {
        // animate (from/to) can be quickly checked first, otherwise we check if any classes are present
        isValidAnimation = (newAnimation.event === 'animate' && Object.keys(newAnimation.options.to || {}).length > 0)
                            || hasAnimationClasses(newAnimation);
      }

      if (!isValidAnimation) {
        close();
        clearElementAnimationState(element);
        return runner;
      }

      // the counter keeps track of cancelled animations
      var counter = (existingAnimation.counter || 0) + 1;
      newAnimation.counter = counter;

      markElementAnimationState(element, PRE_DIGEST_STATE, newAnimation);

      $rootScope.$$postDigest(function() {
        var animationDetails = activeAnimationsLookup.get(node);
        var animationCancelled = !animationDetails;
        animationDetails = animationDetails || {};

        // if addClass/removeClass is called before something like enter then the
        // registered parent element may not be present. The code below will ensure
        // that a final value for parent element is obtained
        var parentElement = element.parent() || [];

        // animate/structural/class-based animations all have requirements. Otherwise there
        // is no point in performing an animation. The parent node must also be set.
        var isValidAnimation = parentElement.length > 0
                                && (animationDetails.event === 'animate'
                                    || animationDetails.structural
                                    || hasAnimationClasses(animationDetails));

        // this means that the previous animation was cancelled
        // even if the follow-up animation is the same event
        if (animationCancelled || animationDetails.counter !== counter || !isValidAnimation) {
          // if another animation did not take over then we need
          // to make sure that the domOperation and options are
          // handled accordingly
          if (animationCancelled) {
            applyAnimationClasses(element, options);
            applyAnimationStyles(element, options);
          }

          // if the event changed from something like enter to leave then we do
          // it, otherwise if it's the same then the end result will be the same too
          if (animationCancelled || (isStructural && animationDetails.event !== event)) {
            options.domOperation();
            runner.end();
          }

          // in the event that the element animation was not cancelled or a follow-up animation
          // isn't allowed to animate from here then we need to clear the state of the element
          // so that any future animations won't read the expired animation data.
          if (!isValidAnimation) {
            clearElementAnimationState(element);
          }

          return;
        }

        // this combined multiple class to addClass / removeClass into a setClass event
        // so long as a structural event did not take over the animation
        event = !animationDetails.structural && hasAnimationClasses(animationDetails, true)
            ? 'setClass'
            : animationDetails.event;

        markElementAnimationState(element, RUNNING_STATE);
        var realRunner = $$animation(element, event, animationDetails.options);

        // this will update the runner's flow-control events based on
        // the `realRunner` object.
        runner.setHost(realRunner);
        notifyProgress(runner, event, 'start', {});

        realRunner.done(function(status) {
          close(!status);
          var animationDetails = activeAnimationsLookup.get(node);
          if (animationDetails && animationDetails.counter === counter) {
            clearElementAnimationState(getDomNode(element));
          }
          notifyProgress(runner, event, 'close', {});
        });
      });

      return runner;

      function notifyProgress(runner, event, phase, data) {
        runInNextPostDigestOrNow(function() {
          var callbacks = findCallbacks(parent, element, event);
          if (callbacks.length) {
            // do not optimize this call here to RAF because
            // we don't know how heavy the callback code here will
            // be and if this code is buffered then this can
            // lead to a performance regression.
            $$rAF(function() {
              forEach(callbacks, function(callback) {
                callback(element, phase, data);
              });
              cleanupEventListeners(phase, element);
            });
          } else {
            cleanupEventListeners(phase, element);
          }
        });
        runner.progress(event, phase, data);
      }

      function close(reject) { // jshint ignore:line
        clearGeneratedClasses(element, options);
        applyAnimationClasses(element, options);
        applyAnimationStyles(element, options);
        options.domOperation();
        runner.complete(!reject);
      }
    }

    function closeChildAnimations(element) {
      var node = getDomNode(element);
      var children = node.querySelectorAll('[' + NG_ANIMATE_ATTR_NAME + ']');
      forEach(children, function(child) {
        var state = parseInt(child.getAttribute(NG_ANIMATE_ATTR_NAME));
        var animationDetails = activeAnimationsLookup.get(child);
        if (animationDetails) {
          switch (state) {
            case RUNNING_STATE:
              animationDetails.runner.end();
              /* falls through */
            case PRE_DIGEST_STATE:
              activeAnimationsLookup.remove(child);
              break;
          }
        }
      });
    }

    function clearElementAnimationState(element) {
      var node = getDomNode(element);
      node.removeAttribute(NG_ANIMATE_ATTR_NAME);
      activeAnimationsLookup.remove(node);
    }

    function isMatchingElement(nodeOrElmA, nodeOrElmB) {
      return getDomNode(nodeOrElmA) === getDomNode(nodeOrElmB);
    }

    /**
     * This fn returns false if any of the following is true:
     * a) animations on any parent element are disabled, and animations on the element aren't explicitly allowed
     * b) a parent element has an ongoing structural animation, and animateChildren is false
     * c) the element is not a child of the body
     * d) the element is not a child of the $rootElement
     */
    function areAnimationsAllowed(element, parentElement, event) {
      var bodyElement = jqLite($document[0].body);
      var bodyElementDetected = isMatchingElement(element, bodyElement) || element[0].nodeName === 'HTML';
      var rootElementDetected = isMatchingElement(element, $rootElement);
      var parentAnimationDetected = false;
      var animateChildren;
      var elementDisabled = disabledElementsLookup.get(getDomNode(element));

      var parentHost = jqLite.data(element[0], NG_ANIMATE_PIN_DATA);
      if (parentHost) {
        parentElement = parentHost;
      }

      parentElement = getDomNode(parentElement);

      while (parentElement) {
        if (!rootElementDetected) {
          // angular doesn't want to attempt to animate elements outside of the application
          // therefore we need to ensure that the rootElement is an ancestor of the current element
          rootElementDetected = isMatchingElement(parentElement, $rootElement);
        }

        if (parentElement.nodeType !== ELEMENT_NODE) {
          // no point in inspecting the #document element
          break;
        }

        var details = activeAnimationsLookup.get(parentElement) || {};
        // either an enter, leave or move animation will commence
        // therefore we can't allow any animations to take place
        // but if a parent animation is class-based then that's ok
        if (!parentAnimationDetected) {
          var parentElementDisabled = disabledElementsLookup.get(parentElement);

          if (parentElementDisabled === true && elementDisabled !== false) {
            // disable animations if the user hasn't explicitly enabled animations on the
            // current element
            elementDisabled = true;
            // element is disabled via parent element, no need to check anything else
            break;
          } else if (parentElementDisabled === false) {
            elementDisabled = false;
          }
          parentAnimationDetected = details.structural;
        }

        if (isUndefined(animateChildren) || animateChildren === true) {
          var value = jqLite.data(parentElement, NG_ANIMATE_CHILDREN_DATA);
          if (isDefined(value)) {
            animateChildren = value;
          }
        }

        // there is no need to continue traversing at this point
        if (parentAnimationDetected && animateChildren === false) break;

        if (!bodyElementDetected) {
          // we also need to ensure that the element is or will be a part of the body element
          // otherwise it is pointless to even issue an animation to be rendered
          bodyElementDetected = isMatchingElement(parentElement, bodyElement);
        }

        if (bodyElementDetected && rootElementDetected) {
          // If both body and root have been found, any other checks are pointless,
          // as no animation data should live outside the application
          break;
        }

        if (!rootElementDetected) {
          // If no rootElement is detected, check if the parentElement is pinned to another element
          parentHost = jqLite.data(parentElement, NG_ANIMATE_PIN_DATA);
          if (parentHost) {
            // The pin target element becomes the next parent element
            parentElement = getDomNode(parentHost);
            continue;
          }
        }

        parentElement = parentElement.parentNode;
      }

      var allowAnimation = (!parentAnimationDetected || animateChildren) && elementDisabled !== true;
      return allowAnimation && rootElementDetected && bodyElementDetected;
    }

    function markElementAnimationState(element, state, details) {
      details = details || {};
      details.state = state;

      var node = getDomNode(element);
      node.setAttribute(NG_ANIMATE_ATTR_NAME, state);

      var oldValue = activeAnimationsLookup.get(node);
      var newValue = oldValue
          ? extend(oldValue, details)
          : details;
      activeAnimationsLookup.put(node, newValue);
    }
  }];
}];

var $$AnimationProvider = ['$animateProvider', function($animateProvider) {
  var NG_ANIMATE_REF_ATTR = 'ng-animate-ref';

  var drivers = this.drivers = [];

  var RUNNER_STORAGE_KEY = '$$animationRunner';

  function setRunner(element, runner) {
    element.data(RUNNER_STORAGE_KEY, runner);
  }

  function removeRunner(element) {
    element.removeData(RUNNER_STORAGE_KEY);
  }

  function getRunner(element) {
    return element.data(RUNNER_STORAGE_KEY);
  }

  this.$get = ['$$jqLite', '$rootScope', '$injector', '$$AnimateRunner', '$$HashMap', '$$rAFScheduler',
       function($$jqLite,   $rootScope,   $injector,   $$AnimateRunner,   $$HashMap,   $$rAFScheduler) {

    var animationQueue = [];
    var applyAnimationClasses = applyAnimationClassesFactory($$jqLite);

    function sortAnimations(animations) {
      var tree = { children: [] };
      var i, lookup = new $$HashMap();

      // this is done first beforehand so that the hashmap
      // is filled with a list of the elements that will be animated
      for (i = 0; i < animations.length; i++) {
        var animation = animations[i];
        lookup.put(animation.domNode, animations[i] = {
          domNode: animation.domNode,
          fn: animation.fn,
          children: []
        });
      }

      for (i = 0; i < animations.length; i++) {
        processNode(animations[i]);
      }

      return flatten(tree);

      function processNode(entry) {
        if (entry.processed) return entry;
        entry.processed = true;

        var elementNode = entry.domNode;
        var parentNode = elementNode.parentNode;
        lookup.put(elementNode, entry);

        var parentEntry;
        while (parentNode) {
          parentEntry = lookup.get(parentNode);
          if (parentEntry) {
            if (!parentEntry.processed) {
              parentEntry = processNode(parentEntry);
            }
            break;
          }
          parentNode = parentNode.parentNode;
        }

        (parentEntry || tree).children.push(entry);
        return entry;
      }

      function flatten(tree) {
        var result = [];
        var queue = [];
        var i;

        for (i = 0; i < tree.children.length; i++) {
          queue.push(tree.children[i]);
        }

        var remainingLevelEntries = queue.length;
        var nextLevelEntries = 0;
        var row = [];

        for (i = 0; i < queue.length; i++) {
          var entry = queue[i];
          if (remainingLevelEntries <= 0) {
            remainingLevelEntries = nextLevelEntries;
            nextLevelEntries = 0;
            result.push(row);
            row = [];
          }
          row.push(entry.fn);
          entry.children.forEach(function(childEntry) {
            nextLevelEntries++;
            queue.push(childEntry);
          });
          remainingLevelEntries--;
        }

        if (row.length) {
          result.push(row);
        }

        return result;
      }
    }

    // TODO(matsko): document the signature in a better way
    return function(element, event, options) {
      options = prepareAnimationOptions(options);
      var isStructural = ['enter', 'move', 'leave'].indexOf(event) >= 0;

      // there is no animation at the current moment, however
      // these runner methods will get later updated with the
      // methods leading into the driver's end/cancel methods
      // for now they just stop the animation from starting
      var runner = new $$AnimateRunner({
        end: function() { close(); },
        cancel: function() { close(true); }
      });

      if (!drivers.length) {
        close();
        return runner;
      }

      setRunner(element, runner);

      var classes = mergeClasses(element.attr('class'), mergeClasses(options.addClass, options.removeClass));
      var tempClasses = options.tempClasses;
      if (tempClasses) {
        classes += ' ' + tempClasses;
        options.tempClasses = null;
      }

      var prepareClassName;
      if (isStructural) {
        prepareClassName = 'ng-' + event + PREPARE_CLASS_SUFFIX;
        $$jqLite.addClass(element, prepareClassName);
      }

      animationQueue.push({
        // this data is used by the postDigest code and passed into
        // the driver step function
        element: element,
        classes: classes,
        event: event,
        structural: isStructural,
        options: options,
        beforeStart: beforeStart,
        close: close
      });

      element.on('$destroy', handleDestroyedElement);

      // we only want there to be one function called within the post digest
      // block. This way we can group animations for all the animations that
      // were apart of the same postDigest flush call.
      if (animationQueue.length > 1) return runner;

      $rootScope.$$postDigest(function() {
        var animations = [];
        forEach(animationQueue, function(entry) {
          // the element was destroyed early on which removed the runner
          // form its storage. This means we can't animate this element
          // at all and it already has been closed due to destruction.
          if (getRunner(entry.element)) {
            animations.push(entry);
          } else {
            entry.close();
          }
        });

        // now any future animations will be in another postDigest
        animationQueue.length = 0;

        var groupedAnimations = groupAnimations(animations);
        var toBeSortedAnimations = [];

        forEach(groupedAnimations, function(animationEntry) {
          toBeSortedAnimations.push({
            domNode: getDomNode(animationEntry.from ? animationEntry.from.element : animationEntry.element),
            fn: function triggerAnimationStart() {
              // it's important that we apply the `ng-animate` CSS class and the
              // temporary classes before we do any driver invoking since these
              // CSS classes may be required for proper CSS detection.
              animationEntry.beforeStart();

              var startAnimationFn, closeFn = animationEntry.close;

              // in the event that the element was removed before the digest runs or
              // during the RAF sequencing then we should not trigger the animation.
              var targetElement = animationEntry.anchors
                  ? (animationEntry.from.element || animationEntry.to.element)
                  : animationEntry.element;

              if (getRunner(targetElement)) {
                var operation = invokeFirstDriver(animationEntry);
                if (operation) {
                  startAnimationFn = operation.start;
                }
              }

              if (!startAnimationFn) {
                closeFn();
              } else {
                var animationRunner = startAnimationFn();
                animationRunner.done(function(status) {
                  closeFn(!status);
                });
                updateAnimationRunners(animationEntry, animationRunner);
              }
            }
          });
        });

        // we need to sort each of the animations in order of parent to child
        // relationships. This ensures that the child classes are applied at the
        // right time.
        $$rAFScheduler(sortAnimations(toBeSortedAnimations));
      });

      return runner;

      // TODO(matsko): change to reference nodes
      function getAnchorNodes(node) {
        var SELECTOR = '[' + NG_ANIMATE_REF_ATTR + ']';
        var items = node.hasAttribute(NG_ANIMATE_REF_ATTR)
              ? [node]
              : node.querySelectorAll(SELECTOR);
        var anchors = [];
        forEach(items, function(node) {
          var attr = node.getAttribute(NG_ANIMATE_REF_ATTR);
          if (attr && attr.length) {
            anchors.push(node);
          }
        });
        return anchors;
      }

      function groupAnimations(animations) {
        var preparedAnimations = [];
        var refLookup = {};
        forEach(animations, function(animation, index) {
          var element = animation.element;
          var node = getDomNode(element);
          var event = animation.event;
          var enterOrMove = ['enter', 'move'].indexOf(event) >= 0;
          var anchorNodes = animation.structural ? getAnchorNodes(node) : [];

          if (anchorNodes.length) {
            var direction = enterOrMove ? 'to' : 'from';

            forEach(anchorNodes, function(anchor) {
              var key = anchor.getAttribute(NG_ANIMATE_REF_ATTR);
              refLookup[key] = refLookup[key] || {};
              refLookup[key][direction] = {
                animationID: index,
                element: jqLite(anchor)
              };
            });
          } else {
            preparedAnimations.push(animation);
          }
        });

        var usedIndicesLookup = {};
        var anchorGroups = {};
        forEach(refLookup, function(operations, key) {
          var from = operations.from;
          var to = operations.to;

          if (!from || !to) {
            // only one of these is set therefore we can't have an
            // anchor animation since all three pieces are required
            var index = from ? from.animationID : to.animationID;
            var indexKey = index.toString();
            if (!usedIndicesLookup[indexKey]) {
              usedIndicesLookup[indexKey] = true;
              preparedAnimations.push(animations[index]);
            }
            return;
          }

          var fromAnimation = animations[from.animationID];
          var toAnimation = animations[to.animationID];
          var lookupKey = from.animationID.toString();
          if (!anchorGroups[lookupKey]) {
            var group = anchorGroups[lookupKey] = {
              structural: true,
              beforeStart: function() {
                fromAnimation.beforeStart();
                toAnimation.beforeStart();
              },
              close: function() {
                fromAnimation.close();
                toAnimation.close();
              },
              classes: cssClassesIntersection(fromAnimation.classes, toAnimation.classes),
              from: fromAnimation,
              to: toAnimation,
              anchors: [] // TODO(matsko): change to reference nodes
            };

            // the anchor animations require that the from and to elements both have at least
            // one shared CSS class which effectively marries the two elements together to use
            // the same animation driver and to properly sequence the anchor animation.
            if (group.classes.length) {
              preparedAnimations.push(group);
            } else {
              preparedAnimations.push(fromAnimation);
              preparedAnimations.push(toAnimation);
            }
          }

          anchorGroups[lookupKey].anchors.push({
            'out': from.element, 'in': to.element
          });
        });

        return preparedAnimations;
      }

      function cssClassesIntersection(a,b) {
        a = a.split(' ');
        b = b.split(' ');
        var matches = [];

        for (var i = 0; i < a.length; i++) {
          var aa = a[i];
          if (aa.substring(0,3) === 'ng-') continue;

          for (var j = 0; j < b.length; j++) {
            if (aa === b[j]) {
              matches.push(aa);
              break;
            }
          }
        }

        return matches.join(' ');
      }

      function invokeFirstDriver(animationDetails) {
        // we loop in reverse order since the more general drivers (like CSS and JS)
        // may attempt more elements, but custom drivers are more particular
        for (var i = drivers.length - 1; i >= 0; i--) {
          var driverName = drivers[i];
          if (!$injector.has(driverName)) continue; // TODO(matsko): remove this check

          var factory = $injector.get(driverName);
          var driver = factory(animationDetails);
          if (driver) {
            return driver;
          }
        }
      }

      function beforeStart() {
        element.addClass(NG_ANIMATE_CLASSNAME);
        if (tempClasses) {
          $$jqLite.addClass(element, tempClasses);
        }
        if (prepareClassName) {
          $$jqLite.removeClass(element, prepareClassName);
          prepareClassName = null;
        }
      }

      function updateAnimationRunners(animation, newRunner) {
        if (animation.from && animation.to) {
          update(animation.from.element);
          update(animation.to.element);
        } else {
          update(animation.element);
        }

        function update(element) {
          var runner = getRunner(element);
          if (runner) runner.setHost(newRunner);
        }
      }

      function handleDestroyedElement() {
        var runner = getRunner(element);
        if (runner && (event !== 'leave' || !options.$$domOperationFired)) {
          runner.end();
        }
      }

      function close(rejected) { // jshint ignore:line
        element.off('$destroy', handleDestroyedElement);
        removeRunner(element);

        applyAnimationClasses(element, options);
        applyAnimationStyles(element, options);
        options.domOperation();

        if (tempClasses) {
          $$jqLite.removeClass(element, tempClasses);
        }

        element.removeClass(NG_ANIMATE_CLASSNAME);
        runner.complete(!rejected);
      }
    };
  }];
}];

/**
 * @ngdoc directive
 * @name ngAnimateSwap
 * @restrict A
 * @scope
 *
 * @description
 *
 * ngAnimateSwap is a animation-oriented directive that allows for the container to
 * be removed and entered in whenever the associated expression changes. A
 * common usecase for this directive is a rotating banner or slider component which
 * contains one image being present at a time. When the active image changes
 * then the old image will perform a `leave` animation and the new element
 * will be inserted via an `enter` animation.
 *
 * @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 the DOM |
 *
 * @example
 * <example name="ngAnimateSwap-directive" module="ngAnimateSwapExample"
 *          deps="angular-animate.js"
 *          animations="true" fixBase="true">
 *   <file name="index.html">
 *     <div class="container" ng-controller="AppCtrl">
 *       <div ng-animate-swap="number" class="cell swap-animation" ng-class="colorClass(number)">
 *         {{ number }}
 *       </div>
 *     </div>
 *   </file>
 *   <file name="script.js">
 *     angular.module('ngAnimateSwapExample', ['ngAnimate'])
 *       .controller('AppCtrl', ['$scope', '$interval', function($scope, $interval) {
 *         $scope.number = 0;
 *         $interval(function() {
 *           $scope.number++;
 *         }, 1000);
 *
 *         var colors = ['red','blue','green','yellow','orange'];
 *         $scope.colorClass = function(number) {
 *           return colors[number % colors.length];
 *         };
 *       }]);
 *   </file>
 *  <file name="animations.css">
 *  .container {
 *    height:250px;
 *    width:250px;
 *    position:relative;
 *    overflow:hidden;
 *    border:2px solid black;
 *  }
 *  .container .cell {
 *    font-size:150px;
 *    text-align:center;
 *    line-height:250px;
 *    position:absolute;
 *    top:0;
 *    left:0;
 *    right:0;
 *    border-bottom:2px solid black;
 *  }
 *  .swap-animation.ng-enter, .swap-animation.ng-leave {
 *    transition:0.5s linear all;
 *  }
 *  .swap-animation.ng-enter {
 *    top:-250px;
 *  }
 *  .swap-animation.ng-enter-active {
 *    top:0px;
 *  }
 *  .swap-animation.ng-leave {
 *    top:0px;
 *  }
 *  .swap-animation.ng-leave-active {
 *    top:250px;
 *  }
 *  .red { background:red; }
 *  .green { background:green; }
 *  .blue { background:blue; }
 *  .yellow { background:yellow; }
 *  .orange { background:orange; }
 *  </file>
 * </example>
 */
var ngAnimateSwapDirective = ['$animate', '$rootScope', function($animate, $rootScope) {
  return {
    restrict: 'A',
    transclude: 'element',
    terminal: true,
    priority: 600, // we use 600 here to ensure that the directive is caught before others
    link: function(scope, $element, attrs, ctrl, $transclude) {
      var previousElement, previousScope;
      scope.$watchCollection(attrs.ngAnimateSwap || attrs['for'], function(value) {
        if (previousElement) {
          $animate.leave(previousElement);
        }
        if (previousScope) {
          previousScope.$destroy();
          previousScope = null;
        }
        if (value || value === 0) {
          previousScope = scope.$new();
          $transclude(previousScope, function(element) {
            previousElement = element;
            $animate.enter(element, null, $element);
          });
        }
      });
    }
  };
}];

/* global angularAnimateModule: true,

   ngAnimateSwapDirective,
   $$AnimateAsyncRunFactory,
   $$rAFSchedulerFactory,
   $$AnimateChildrenDirective,
   $$AnimateQueueProvider,
   $$AnimationProvider,
   $AnimateCssProvider,
   $$AnimateCssDriverProvider,
   $$AnimateJsProvider,
   $$AnimateJsDriverProvider,
*/

/**
 * @ngdoc module
 * @name ngAnimate
 * @description
 *
 * The `ngAnimate` module provides support for CSS-based animations (keyframes and transitions) as well as JavaScript-based animations via
 * callback hooks. Animations are not enabled by default, however, by including `ngAnimate` the animation hooks are enabled for an Angular app.
 *
 * <div doc-module-components="ngAnimate"></div>
 *
 * # Usage
 * Simply put, there are two ways to make use of animations when ngAnimate is used: by using **CSS** and **JavaScript**. The former works purely based
 * using CSS (by using matching CSS selectors/styles) and the latter triggers animations that are registered via `module.animation()`. For
 * both CSS and JS animations the sole requirement is to have a matching `CSS class` that exists both in the registered animation and within
 * the HTML element that the animation will be triggered on.
 *
 * ## Directive Support
 * The following directives are "animation aware":
 *
 * | Directive                                                                                                | Supported Animations                                                     |
 * |----------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------|
 * | {@link ng.directive:ngRepeat#animations ngRepeat}                                                        | enter, leave and move                                                    |
 * | {@link ngRoute.directive:ngView#animations ngView}                                                       | enter and leave                                                          |
 * | {@link ng.directive:ngInclude#animations ngInclude}                                                      | enter and leave                                                          |
 * | {@link ng.directive:ngSwitch#animations ngSwitch}                                                        | enter and leave                                                          |
 * | {@link ng.directive:ngIf#animations ngIf}                                                                | enter and leave                                                          |
 * | {@link ng.directive:ngClass#animations ngClass}                                                          | add and remove (the CSS class(es) present)                               |
 * | {@link ng.directive:ngShow#animations ngShow} & {@link ng.directive:ngHide#animations ngHide}            | add and remove (the ng-hide class value)                                 |
 * | {@link ng.directive:form#animation-hooks form} & {@link ng.directive:ngModel#animation-hooks ngModel}    | add and remove (dirty, pristine, valid, invalid & all other validations) |
 * | {@link module:ngMessages#animations ngMessages}                                                          | add and remove (ng-active & ng-inactive)                                 |
 * | {@link module:ngMessages#animations ngMessage}                                                           | enter and leave                                                          |
 *
 * (More information can be found by visiting each the documentation associated with each directive.)
 *
 * ## CSS-based Animations
 *
 * CSS-based animations with ngAnimate are unique since they require no JavaScript code at all. By using a CSS class that we reference between our HTML
 * and CSS code we can create an animation that will be picked up by Angular when an the underlying directive performs an operation.
 *
 * The example below shows how an `enter` animation can be made possible on an element using `ng-if`:
 *
 * ```html
 * <div ng-if="bool" class="fade">
 *    Fade me in out
 * </div>
 * <button ng-click="bool=true">Fade In!</button>
 * <button ng-click="bool=false">Fade Out!</button>
 * ```
 *
 * Notice the CSS class **fade**? We can now create the CSS transition code that references this class:
 *
 * ```css
 * /&#42; The starting CSS styles for the enter animation &#42;/
 * .fade.ng-enter {
 *   transition:0.5s linear all;
 *   opacity:0;
 * }
 *
 * /&#42; The finishing CSS styles for the enter animation &#42;/
 * .fade.ng-enter.ng-enter-active {
 *   opacity:1;
 * }
 * ```
 *
 * The key thing to remember here is that, depending on the animation event (which each of the directives above trigger depending on what's going on) two
 * generated CSS classes will be applied to the element; in the example above we have `.ng-enter` and `.ng-enter-active`. For CSS transitions, the transition
 * code **must** be defined within the starting CSS class (in this case `.ng-enter`). The destination class is what the transition will animate towards.
 *
 * If for example we wanted to create animations for `leave` and `move` (ngRepeat triggers move) then we can do so using the same CSS naming conventions:
 *
 * ```css
 * /&#42; now the element will fade out before it is removed from the DOM &#42;/
 * .fade.ng-leave {
 *   transition:0.5s linear all;
 *   opacity:1;
 * }
 * .fade.ng-leave.ng-leave-active {
 *   opacity:0;
 * }
 * ```
 *
 * We can also make use of **CSS Keyframes** by referencing the keyframe animation within the starting CSS class:
 *
 * ```css
 * /&#42; there is no need to define anything inside of the destination
 * CSS class since the keyframe will take charge of the animation &#42;/
 * .fade.ng-leave {
 *   animation: my_fade_animation 0.5s linear;
 *   -webkit-animation: my_fade_animation 0.5s linear;
 * }
 *
 * @keyframes my_fade_animation {
 *   from { opacity:1; }
 *   to { opacity:0; }
 * }
 *
 * @-webkit-keyframes my_fade_animation {
 *   from { opacity:1; }
 *   to { opacity:0; }
 * }
 * ```
 *
 * Feel free also mix transitions and keyframes together as well as any other CSS classes on the same element.
 *
 * ### CSS Class-based Animations
 *
 * Class-based animations (animations that are triggered via `ngClass`, `ngShow`, `ngHide` and some other directives) have a slightly different
 * naming convention. Class-based animations are basic enough that a standard transition or keyframe can be referenced on the class being added
 * and removed.
 *
 * For example if we wanted to do a CSS animation for `ngHide` then we place an animation on the `.ng-hide` CSS class:
 *
 * ```html
 * <div ng-show="bool" class="fade">
 *   Show and hide me
 * </div>
 * <button ng-click="bool=!bool">Toggle</button>
 *
 * <style>
 * .fade.ng-hide {
 *   transition:0.5s linear all;
 *   opacity:0;
 * }
 * </style>
 * ```
 *
 * All that is going on here with ngShow/ngHide behind the scenes is the `.ng-hide` class is added/removed (when the hidden state is valid). Since
 * ngShow and ngHide are animation aware then we can match up a transition and ngAnimate handles the rest.
 *
 * In addition the addition and removal of the CSS class, ngAnimate also provides two helper methods that we can use to further decorate the animation
 * with CSS styles.
 *
 * ```html
 * <div ng-class="{on:onOff}" class="highlight">
 *   Highlight this box
 * </div>
 * <button ng-click="onOff=!onOff">Toggle</button>
 *
 * <style>
 * .highlight {
 *   transition:0.5s linear all;
 * }
 * .highlight.on-add {
 *   background:white;
 * }
 * .highlight.on {
 *   background:yellow;
 * }
 * .highlight.on-remove {
 *   background:black;
 * }
 * </style>
 * ```
 *
 * We can also make use of CSS keyframes by placing them within the CSS classes.
 *
 *
 * ### CSS Staggering Animations
 * A Staggering animation is a collection of animations that are issued with a slight delay in between each successive operation resulting in a
 * curtain-like effect. The ngAnimate module (versions >=1.2) supports staggering animations and the stagger effect can be
 * performed by creating a **ng-EVENT-stagger** CSS class and attaching that class to the base CSS class used for
 * the animation. The style property expected within the stagger class can either be a **transition-delay** or an
 * **animation-delay** property (or both if your animation contains both transitions and keyframe animations).
 *
 * ```css
 * .my-animation.ng-enter {
 *   /&#42; standard transition code &#42;/
 *   transition: 1s linear all;
 *   opacity:0;
 * }
 * .my-animation.ng-enter-stagger {
 *   /&#42; this will have a 100ms delay between each successive leave animation &#42;/
 *   transition-delay: 0.1s;
 *
 *   /&#42; As of 1.4.4, this must always be set: it signals ngAnimate
 *     to not accidentally inherit a delay property from another CSS class &#42;/
 *   transition-duration: 0s;
 * }
 * .my-animation.ng-enter.ng-enter-active {
 *   /&#42; standard transition styles &#42;/
 *   opacity:1;
 * }
 * ```
 *
 * Staggering animations work by default in ngRepeat (so long as the CSS class is defined). Outside of ngRepeat, to use staggering animations
 * on your own, they can be triggered by firing multiple calls to the same event on $animate. However, the restrictions surrounding this
 * are that each of the elements must have the same CSS className value as well as the same parent element. A stagger operation
 * will also be reset if one or more animation frames have passed since the multiple calls to `$animate` were fired.
 *
 * The following code will issue the **ng-leave-stagger** event on the element provided:
 *
 * ```js
 * var kids = parent.children();
 *
 * $animate.leave(kids[0]); //stagger index=0
 * $animate.leave(kids[1]); //stagger index=1
 * $animate.leave(kids[2]); //stagger index=2
 * $animate.leave(kids[3]); //stagger index=3
 * $animate.leave(kids[4]); //stagger index=4
 *
 * window.requestAnimationFrame(function() {
 *   //stagger has reset itself
 *   $animate.leave(kids[5]); //stagger index=0
 *   $animate.leave(kids[6]); //stagger index=1
 *
 *   $scope.$digest();
 * });
 * ```
 *
 * Stagger animations are currently only supported within CSS-defined animations.
 *
 * ### The `ng-animate` CSS class
 *
 * When ngAnimate is animating an element it will apply the `ng-animate` CSS class to the element for the duration of the animation.
 * This is a temporary CSS class and it will be removed once the animation is over (for both JavaScript and CSS-based animations).
 *
 * Therefore, animations can be applied to an element using this temporary class directly via CSS.
 *
 * ```css
 * .zipper.ng-animate {
 *   transition:0.5s linear all;
 * }
 * .zipper.ng-enter {
 *   opacity:0;
 * }
 * .zipper.ng-enter.ng-enter-active {
 *   opacity:1;
 * }
 * .zipper.ng-leave {
 *   opacity:1;
 * }
 * .zipper.ng-leave.ng-leave-active {
 *   opacity:0;
 * }
 * ```
 *
 * (Note that the `ng-animate` CSS class is reserved and it cannot be applied on an element directly since ngAnimate will always remove
 * the CSS class once an animation has completed.)
 *
 *
 * ### The `ng-[event]-prepare` class
 *
 * This is a special class that can be used to prevent unwanted flickering / flash of content before
 * the actual animation starts. The class is added as soon as an animation is initialized, but removed
 * before the actual animation starts (after waiting for a $digest).
 * It is also only added for *structural* animations (`enter`, `move`, and `leave`).
 *
 * In practice, flickering can appear when nesting elements with structural animations such as `ngIf`
 * into elements that have class-based animations such as `ngClass`.
 *
 * ```html
 * <div ng-class="{red: myProp}">
 *   <div ng-class="{blue: myProp}">
 *     <div class="message" ng-if="myProp"></div>
 *   </div>
 * </div>
 * ```
 *
 * It is possible that during the `enter` animation, the `.message` div will be briefly visible before it starts animating.
 * In that case, you can add styles to the CSS that make sure the element stays hidden before the animation starts:
 *
 * ```css
 * .message.ng-enter-prepare {
 *   opacity: 0;
 * }
 *
 * ```
 *
 * ## JavaScript-based Animations
 *
 * ngAnimate also allows for animations to be consumed by JavaScript code. The approach is similar to CSS-based animations (where there is a shared
 * CSS class that is referenced in our HTML code) but in addition we need to register the JavaScript animation on the module. By making use of the
 * `module.animation()` module function we can register the animation.
 *
 * Let's see an example of a enter/leave animation using `ngRepeat`:
 *
 * ```html
 * <div ng-repeat="item in items" class="slide">
 *   {{ item }}
 * </div>
 * ```
 *
 * See the **slide** CSS class? Let's use that class to define an animation that we'll structure in our module code by using `module.animation`:
 *
 * ```js
 * myModule.animation('.slide', [function() {
 *   return {
 *     // make note that other events (like addClass/removeClass)
 *     // have different function input parameters
 *     enter: function(element, doneFn) {
 *       jQuery(element).fadeIn(1000, doneFn);
 *
 *       // remember to call doneFn so that angular
 *       // knows that the animation has concluded
 *     },
 *
 *     move: function(element, doneFn) {
 *       jQuery(element).fadeIn(1000, doneFn);
 *     },
 *
 *     leave: function(element, doneFn) {
 *       jQuery(element).fadeOut(1000, doneFn);
 *     }
 *   }
 * }]);
 * ```
 *
 * The nice thing about JS-based animations is that we can inject other services and make use of advanced animation libraries such as
 * greensock.js and velocity.js.
 *
 * If our animation code class-based (meaning that something like `ngClass`, `ngHide` and `ngShow` triggers it) then we can still define
 * our animations inside of the same registered animation, however, the function input arguments are a bit different:
 *
 * ```html
 * <div ng-class="color" class="colorful">
 *   this box is moody
 * </div>
 * <button ng-click="color='red'">Change to red</button>
 * <button ng-click="color='blue'">Change to blue</button>
 * <button ng-click="color='green'">Change to green</button>
 * ```
 *
 * ```js
 * myModule.animation('.colorful', [function() {
 *   return {
 *     addClass: function(element, className, doneFn) {
 *       // do some cool animation and call the doneFn
 *     },
 *     removeClass: function(element, className, doneFn) {
 *       // do some cool animation and call the doneFn
 *     },
 *     setClass: function(element, addedClass, removedClass, doneFn) {
 *       // do some cool animation and call the doneFn
 *     }
 *   }
 * }]);
 * ```
 *
 * ## CSS + JS Animations Together
 *
 * AngularJS 1.4 and higher has taken steps to make the amalgamation of CSS and JS animations more flexible. However, unlike earlier versions of Angular,
 * defining CSS and JS animations to work off of the same CSS class will not work anymore. Therefore the example below will only result in **JS animations taking
 * charge of the animation**:
 *
 * ```html
 * <div ng-if="bool" class="slide">
 *   Slide in and out
 * </div>
 * ```
 *
 * ```js
 * myModule.animation('.slide', [function() {
 *   return {
 *     enter: function(element, doneFn) {
 *       jQuery(element).slideIn(1000, doneFn);
 *     }
 *   }
 * }]);
 * ```
 *
 * ```css
 * .slide.ng-enter {
 *   transition:0.5s linear all;
 *   transform:translateY(-100px);
 * }
 * .slide.ng-enter.ng-enter-active {
 *   transform:translateY(0);
 * }
 * ```
 *
 * Does this mean that CSS and JS animations cannot be used together? Do JS-based animations always have higher priority? We can make up for the
 * lack of CSS animations by using the `$animateCss` service to trigger our own tweaked-out, CSS-based animations directly from
 * our own JS-based animation code:
 *
 * ```js
 * myModule.animation('.slide', ['$animateCss', function($animateCss) {
 *   return {
 *     enter: function(element) {
*        // this will trigger `.slide.ng-enter` and `.slide.ng-enter-active`.
 *       return $animateCss(element, {
 *         event: 'enter',
 *         structural: true
 *       });
 *     }
 *   }
 * }]);
 * ```
 *
 * The nice thing here is that we can save bandwidth by sticking to our CSS-based animation code and we don't need to rely on a 3rd-party animation framework.
 *
 * The `$animateCss` service is very powerful since we can feed in all kinds of extra properties that will be evaluated and fed into a CSS transition or
 * keyframe animation. For example if we wanted to animate the height of an element while adding and removing classes then we can do so by providing that
 * data into `$animateCss` directly:
 *
 * ```js
 * myModule.animation('.slide', ['$animateCss', function($animateCss) {
 *   return {
 *     enter: function(element) {
 *       return $animateCss(element, {
 *         event: 'enter',
 *         structural: true,
 *         addClass: 'maroon-setting',
 *         from: { height:0 },
 *         to: { height: 200 }
 *       });
 *     }
 *   }
 * }]);
 * ```
 *
 * Now we can fill in the rest via our transition CSS code:
 *
 * ```css
 * /&#42; the transition tells ngAnimate to make the animation happen &#42;/
 * .slide.ng-enter { transition:0.5s linear all; }
 *
 * /&#42; this extra CSS class will be absorbed into the transition
 * since the $animateCss code is adding the class &#42;/
 * .maroon-setting { background:red; }
 * ```
 *
 * And `$animateCss` will figure out the rest. Just make sure to have the `done()` callback fire the `doneFn` function to signal when the animation is over.
 *
 * To learn more about what's possible be sure to visit the {@link ngAnimate.$animateCss $animateCss service}.
 *
 * ## Animation Anchoring (via `ng-animate-ref`)
 *
 * ngAnimate in AngularJS 1.4 comes packed with the ability to cross-animate elements between
 * structural areas of an application (like views) by pairing up elements using an attribute
 * called `ng-animate-ref`.
 *
 * Let's say for example we have two views that are managed by `ng-view` and we want to show
 * that there is a relationship between two components situated in within these views. By using the
 * `ng-animate-ref` attribute we can identify that the two components are paired together and we
 * can then attach an animation, which is triggered when the view changes.
 *
 * Say for example we have the following template code:
 *
 * ```html
 * <!-- index.html -->
 * <div ng-view class="view-animation">
 * </div>
 *
 * <!-- home.html -->
 * <a href="#/banner-page">
 *   <img src="./banner.jpg" class="banner" ng-animate-ref="banner">
 * </a>
 *
 * <!-- banner-page.html -->
 * <img src="./banner.jpg" class="banner" ng-animate-ref="banner">
 * ```
 *
 * Now, when the view changes (once the link is clicked), ngAnimate will examine the
 * HTML contents to see if there is a match reference between any components in the view
 * that is leaving and the view that is entering. It will scan both the view which is being
 * removed (leave) and inserted (enter) to see if there are any paired DOM elements that
 * contain a matching ref value.
 *
 * The two images match since they share the same ref value. ngAnimate will now create a
 * transport element (which is a clone of the first image element) and it will then attempt
 * to animate to the position of the second image element in the next view. For the animation to
 * work a special CSS class called `ng-anchor` will be added to the transported element.
 *
 * We can now attach a transition onto the `.banner.ng-anchor` CSS class and then
 * ngAnimate will handle the entire transition for us as well as the addition and removal of
 * any changes of CSS classes between the elements:
 *
 * ```css
 * .banner.ng-anchor {
 *   /&#42; this animation will last for 1 second since there are
 *          two phases to the animation (an `in` and an `out` phase) &#42;/
 *   transition:0.5s linear all;
 * }
 * ```
 *
 * We also **must** include animations for the views that are being entered and removed
 * (otherwise anchoring wouldn't be possible since the new view would be inserted right away).
 *
 * ```css
 * .view-animation.ng-enter, .view-animation.ng-leave {
 *   transition:0.5s linear all;
 *   position:fixed;
 *   left:0;
 *   top:0;
 *   width:100%;
 * }
 * .view-animation.ng-enter {
 *   transform:translateX(100%);
 * }
 * .view-animation.ng-leave,
 * .view-animation.ng-enter.ng-enter-active {
 *   transform:translateX(0%);
 * }
 * .view-animation.ng-leave.ng-leave-active {
 *   transform:translateX(-100%);
 * }
 * ```
 *
 * Now we can jump back to the anchor animation. When the animation happens, there are two stages that occur:
 * an `out` and an `in` stage. The `out` stage happens first and that is when the element is animated away
 * from its origin. Once that animation is over then the `in` stage occurs which animates the
 * element to its destination. The reason why there are two animations is to give enough time
 * for the enter animation on the new element to be ready.
 *
 * The example above sets up a transition for both the in and out phases, but we can also target the out or
 * in phases directly via `ng-anchor-out` and `ng-anchor-in`.
 *
 * ```css
 * .banner.ng-anchor-out {
 *   transition: 0.5s linear all;
 *
 *   /&#42; the scale will be applied during the out animation,
 *          but will be animated away when the in animation runs &#42;/
 *   transform: scale(1.2);
 * }
 *
 * .banner.ng-anchor-in {
 *   transition: 1s linear all;
 * }
 * ```
 *
 *
 *
 *
 * ### Anchoring Demo
 *
  <example module="anchoringExample"
           name="anchoringExample"
           id="anchoringExample"
           deps="angular-animate.js;angular-route.js"
           animations="true">
    <file name="index.html">
      <a href="#/">Home</a>
      <hr />
      <div class="view-container">
        <div ng-view class="view"></div>
      </div>
    </file>
    <file name="script.js">
      angular.module('anchoringExample', ['ngAnimate', 'ngRoute'])
        .config(['$routeProvider', function($routeProvider) {
          $routeProvider.when('/', {
            templateUrl: 'home.html',
            controller: 'HomeController as home'
          });
          $routeProvider.when('/profile/:id', {
            templateUrl: 'profile.html',
            controller: 'ProfileController as profile'
          });
        }])
        .run(['$rootScope', function($rootScope) {
          $rootScope.records = [
            { id:1, title: "Miss Beulah Roob" },
            { id:2, title: "Trent Morissette" },
            { id:3, title: "Miss Ava Pouros" },
            { id:4, title: "Rod Pouros" },
            { id:5, title: "Abdul Rice" },
            { id:6, title: "Laurie Rutherford Sr." },
            { id:7, title: "Nakia McLaughlin" },
            { id:8, title: "Jordon Blanda DVM" },
            { id:9, title: "Rhoda Hand" },
            { id:10, title: "Alexandrea Sauer" }
          ];
        }])
        .controller('HomeController', [function() {
          //empty
        }])
        .controller('ProfileController', ['$rootScope', '$routeParams', function($rootScope, $routeParams) {
          var index = parseInt($routeParams.id, 10);
          var record = $rootScope.records[index - 1];

          this.title = record.title;
          this.id = record.id;
        }]);
    </file>
    <file name="home.html">
      <h2>Welcome to the home page</h1>
      <p>Please click on an element</p>
      <a class="record"
         ng-href="#/profile/{{ record.id }}"
         ng-animate-ref="{{ record.id }}"
         ng-repeat="record in records">
        {{ record.title }}
      </a>
    </file>
    <file name="profile.html">
      <div class="profile record" ng-animate-ref="{{ profile.id }}">
        {{ profile.title }}
      </div>
    </file>
    <file name="animations.css">
      .record {
        display:block;
        font-size:20px;
      }
      .profile {
        background:black;
        color:white;
        font-size:100px;
      }
      .view-container {
        position:relative;
      }
      .view-container > .view.ng-animate {
        position:absolute;
        top:0;
        left:0;
        width:100%;
        min-height:500px;
      }
      .view.ng-enter, .view.ng-leave,
      .record.ng-anchor {
        transition:0.5s linear all;
      }
      .view.ng-enter {
        transform:translateX(100%);
      }
      .view.ng-enter.ng-enter-active, .view.ng-leave {
        transform:translateX(0%);
      }
      .view.ng-leave.ng-leave-active {
        transform:translateX(-100%);
      }
      .record.ng-anchor-out {
        background:red;
      }
    </file>
  </example>
 *
 * ### How is the element transported?
 *
 * When an anchor animation occurs, ngAnimate will clone the starting element and position it exactly where the starting
 * element is located on screen via absolute positioning. The cloned element will be placed inside of the root element
 * of the application (where ng-app was defined) and all of the CSS classes of the starting element will be applied. The
 * element will then animate into the `out` and `in` animations and will eventually reach the coordinates and match
 * the dimensions of the destination element. During the entire animation a CSS class of `.ng-animate-shim` will be applied
 * to both the starting and destination elements in order to hide them from being visible (the CSS styling for the class
 * is: `visibility:hidden`). Once the anchor reaches its destination then it will be removed and the destination element
 * will become visible since the shim class will be removed.
 *
 * ### How is the morphing handled?
 *
 * CSS Anchoring relies on transitions and keyframes and the internal code is intelligent enough to figure out
 * what CSS classes differ between the starting element and the destination element. These different CSS classes
 * will be added/removed on the anchor element and a transition will be applied (the transition that is provided
 * in the anchor class). Long story short, ngAnimate will figure out what classes to add and remove which will
 * make the transition of the element as smooth and automatic as possible. Be sure to use simple CSS classes that
 * do not rely on DOM nesting structure so that the anchor element appears the same as the starting element (since
 * the cloned element is placed inside of root element which is likely close to the body element).
 *
 * Note that if the root element is on the `<html>` element then the cloned node will be placed inside of body.
 *
 *
 * ## Using $animate in your directive code
 *
 * So far we've explored how to feed in animations into an Angular application, but how do we trigger animations within our own directives in our application?
 * By injecting the `$animate` service into our directive code, we can trigger structural and class-based hooks which can then be consumed by animations. Let's
 * imagine we have a greeting box that shows and hides itself when the data changes
 *
 * ```html
 * <greeting-box active="onOrOff">Hi there</greeting-box>
 * ```
 *
 * ```js
 * ngModule.directive('greetingBox', ['$animate', function($animate) {
 *   return function(scope, element, attrs) {
 *     attrs.$observe('active', function(value) {
 *       value ? $animate.addClass(element, 'on') : $animate.removeClass(element, 'on');
 *     });
 *   });
 * }]);
 * ```
 *
 * Now the `on` CSS class is added and removed on the greeting box component. Now if we add a CSS class on top of the greeting box element
 * in our HTML code then we can trigger a CSS or JS animation to happen.
 *
 * ```css
 * /&#42; normally we would create a CSS class to reference on the element &#42;/
 * greeting-box.on { transition:0.5s linear all; background:green; color:white; }
 * ```
 *
 * The `$animate` service contains a variety of other methods like `enter`, `leave`, `animate` and `setClass`. To learn more about what's
 * possible be sure to visit the {@link ng.$animate $animate service API page}.
 *
 *
 * ## Callbacks and Promises
 *
 * When `$animate` is called it returns a promise that can be used to capture when the animation has ended. Therefore if we were to trigger
 * an animation (within our directive code) then we can continue performing directive and scope related activities after the animation has
 * ended by chaining onto the returned promise that animation method returns.
 *
 * ```js
 * // somewhere within the depths of the directive
 * $animate.enter(element, parent).then(function() {
 *   //the animation has completed
 * });
 * ```
 *
 * (Note that earlier versions of Angular prior to v1.4 required the promise code to be wrapped using `$scope.$apply(...)`. This is not the case
 * anymore.)
 *
 * In addition to the animation promise, we can also make use of animation-related callbacks within our directives and controller code by registering
 * an event listener using the `$animate` service. Let's say for example that an animation was triggered on our view
 * routing controller to hook into that:
 *
 * ```js
 * ngModule.controller('HomePageController', ['$animate', function($animate) {
 *   $animate.on('enter', ngViewElement, function(element) {
 *     // the animation for this route has completed
 *   }]);
 * }])
 * ```
 *
 * (Note that you will need to trigger a digest within the callback to get angular to notice any scope-related changes.)
 */

/**
 * @ngdoc service
 * @name $animate
 * @kind object
 *
 * @description
 * The ngAnimate `$animate` service documentation is the same for the core `$animate` service.
 *
 * Click here {@link ng.$animate to learn more about animations with `$animate`}.
 */
angular.module('ngAnimate', [])
  .directive('ngAnimateSwap', ngAnimateSwapDirective)

  .directive('ngAnimateChildren', $$AnimateChildrenDirective)
  .factory('$$rAFScheduler', $$rAFSchedulerFactory)

  .provider('$$animateQueue', $$AnimateQueueProvider)
  .provider('$$animation', $$AnimationProvider)

  .provider('$animateCss', $AnimateCssProvider)
  .provider('$$animateCssDriver', $$AnimateCssDriverProvider)

  .provider('$$animateJs', $$AnimateJsProvider)
  .provider('$$animateJsDriver', $$AnimateJsDriverProvider);


})(window, window.angular);


/*global angular: true, appInsights: true */
var appInsights = window.appInsights || function (config) {function i(config) { t[config] = function () { var i = arguments; t.queue.push(function () { t[config].apply(t, i) }) } } var t = {config: config }, u = document, e = window, o = "script", s = "AuthenticatedUserContext", h = "start", c = "stop", l = "Track", a = l + "Event", v = l + "Page", y = u.createElement(o), r, f; y.src = config.url || "https://az416426.vo.msecnd.net/scripts/a/ai.0.js"; u.getElementsByTagName(o)[0].parentNode.appendChild(y); try {t.cookie = u.cookie} catch (p) {} for (t.queue = [], t.version = "1.0", r = ["Event", "Exception", "Metric", "PageView", "Trace", "Dependency"]; r.length;) i("track" + r.pop()); return i("set" + s), i("clear" + s), i(h + a), i(c + a), i(h + v), i(c + v), i("flush"), config.disableExceptionTracking || (r = "onerror", i("_" + r), f = e[r], e[r] = function (config, i, u, e, o) { var s = f && f(config, i, u, e, o); return s !== !0 && t["_" + r](config, i, u, e, o), s }), t };
window.appInsights = appInsights;

(function () {
    "use strict";

    angular.module('angular-appinsights', [])

        .provider('insights', function () {

            var _appId,
                _appName,
                _appUserId;

            this.start = function (appId, appName, appUserId) {

                _appId = appId;
                _appName = appName || '(Application Root)';
                _appUserId = appUserId || '';

                if (appInsights && appId && appInsights.start) {
                    appInsights.start(appId);
                } 
				if (appInsights && appId && !appInsights.start)
				{
				    appInsights = appInsights({ instrumentationKey: appId, accountId: _appUserId });
				}

            };

            function Insights () {

                function _logEvent(event, properties, property) {

                    if (appInsights && _appId && appInsights.logEvent) {
                        appInsights.logEvent(event, properties, property);
                    }
                    if (appInsights && _appId && appInsights.trackEvent) {
                        appInsights.trackEvent(event, properties, property);
                    }

                };

                function _logPageView(page) {
                    
                    if (appInsights && _appId && appInsights.logPageView) {
                        appInsights.logPageView(page);
                    }
					if (appInsights && _appId && appInsights.trackPageView) {
                        appInsights.trackPageView(page);
                    }

                };

                /**
                 * Function to generate a random guid
                 * @param len - length of desired string
                 * @returns random alpha id
                 */
                function _randomString(len) {
                    let guid = '';
                    const characters = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
                    const charactersLength = characters.length;
                    for (let i = 0; i < len; i++) {
                        guid += characters.substr(Math.floor((Math.random() * charactersLength) + 1), 1);
                    }
                    return guid;
                }

                return {
                    'logEvent': _logEvent,
                    'logPageView': _logPageView,
                    'appName': _appName,
                    'randomString': _randomString
                };
            }

            this.$get = function() {
                return new Insights();
            };

        })

        .run(function($rootScope, $location, insights) {
            $rootScope.$on('$locationChangeSuccess', function() {
                var pagePath;
                try {
                    pagePath = $location.path().substr(1);
                    pagePath =  insights.appName + '/' + pagePath;
                }
                finally {
                    insights.logPageView(pagePath);
                }
            });
        });

}());

/**
 * Amplitude - JavaScript SDK using a small snippet of code
 */

(function (e, t) {
    var n = e.amplitude || { _q: [], _iq: {} };
    var r = t.createElement("script");
    r.type = "text/javascript";
    r.integrity =
        "sha384-girahbTbYZ9tT03PWWj0mEVgyxtZoyDF9KVZdL+R53PP5wCY0PiVUKq0jeRlMx9M";
    r.crossOrigin = "anonymous";
    r.async = true;
    r.src = "https://cdn.amplitude.com/libs/amplitude-7.2.1-min.gz.js";
    r.onload = function () {
        if (!e.amplitude.runQueuedFunctions) {
            console.log("[Amplitude] Error: could not load SDK");
        }
    };
    var i = t.getElementsByTagName("script")[0];
    i.parentNode.insertBefore(r, i);
    function s(e, t) {
        e.prototype[t] = function () {
            this._q.push([t].concat(Array.prototype.slice.call(arguments, 0)));
            return this;
        };
    }
    var o = function () {
        this._q = [];
        return this;
    };
    var a = [
        "add",
        "append",
        "clearAll",
        "prepend",
        "set",
        "setOnce",
        "unset",
    ];
    for (var c = 0; c < a.length; c++) {
        s(o, a[c]);
    }
    n.Identify = o;
    var u = function () {
        this._q = [];
        return this;
    };
    var l = [
        "setProductId",
        "setQuantity",
        "setPrice",
        "setRevenueType",
        "setEventProperties",
    ];
    for (var p = 0; p < l.length; p++) {
        s(u, l[p]);
    }
    n.Revenue = u;
    var d = [
        "init",
        "logEvent",
        "logRevenue",
        "setUserId",
        "setUserProperties",
        "setOptOut",
        "setVersionName",
        "setDomain",
        "setDeviceId",
        "enableTracking",
        "setGlobalUserProperties",
        "identify",
        "clearUserProperties",
        "setGroup",
        "logRevenueV2",
        "regenerateDeviceId",
        "groupIdentify",
        "onInit",
        "logEventWithTimestamp",
        "logEventWithGroups",
        "setSessionId",
        "resetSessionId",
    ];
    function v(e) {
        function t(t) {
            e[t] = function () {
                e._q.push([t].concat(Array.prototype.slice.call(arguments, 0)));
            };
        }
        for (var n = 0; n < d.length; n++) {
            t(d[n]);
        }
    }
    v(n);
    n.getInstance = function (e) {
        e = (!e || e.length === 0 ? "$default_instance" : e).toLowerCase();
        if (!n._iq.hasOwnProperty(e)) {
            n._iq[e] = { _q: [] };
            v(n._iq[e]);
        }
        return n._iq[e];
    };
    e.amplitude = n;
})(window, document);
/*
 * angular-ui-bootstrap
 * http://angular-ui.github.io/bootstrap/

 * Version: 2.1.2 - 2016-08-22
 * License: MIT
 */angular.module("ui.bootstrap", ["ui.bootstrap.tpls", "ui.bootstrap.collapse", "ui.bootstrap.tabindex", "ui.bootstrap.accordion", "ui.bootstrap.alert", "ui.bootstrap.buttons", "ui.bootstrap.carousel", "ui.bootstrap.dateparser", "ui.bootstrap.isClass", "ui.bootstrap.datepicker", "ui.bootstrap.position", "ui.bootstrap.datepickerPopup", "ui.bootstrap.debounce", "ui.bootstrap.dropdown", "ui.bootstrap.stackedMap", "ui.bootstrap.modal", "ui.bootstrap.paging", "ui.bootstrap.pager", "ui.bootstrap.pagination", "ui.bootstrap.tooltip", "ui.bootstrap.popover", "ui.bootstrap.progressbar", "ui.bootstrap.rating", "ui.bootstrap.tabs", "ui.bootstrap.timepicker", "ui.bootstrap.typeahead"]);
angular.module("ui.bootstrap.tpls", ["uib/template/accordion/accordion-group.html", "uib/template/accordion/accordion.html", "uib/template/alert/alert.html", "uib/template/carousel/carousel.html", "uib/template/carousel/slide.html", "uib/template/datepicker/datepicker.html", "uib/template/datepicker/day.html", "uib/template/datepicker/month.html", "uib/template/datepicker/year.html", "uib/template/datepickerPopup/popup.html", "uib/template/modal/window.html", "uib/template/pager/pager.html", "uib/template/pagination/pagination.html", "uib/template/tooltip/tooltip-html-popup.html", "uib/template/tooltip/tooltip-popup.html", "uib/template/tooltip/tooltip-template-popup.html", "uib/template/popover/popover-html.html", "uib/template/popover/popover-template.html", "uib/template/popover/popover.html", "uib/template/progressbar/bar.html", "uib/template/progressbar/progress.html", "uib/template/progressbar/progressbar.html", "uib/template/rating/rating.html", "uib/template/tabs/tab.html", "uib/template/tabs/tabset.html", "uib/template/timepicker/timepicker.html", "uib/template/typeahead/typeahead-match.html", "uib/template/typeahead/typeahead-popup.html"]);
angular.module('ui.bootstrap.collapse', [])

  .directive('uibCollapse', ['$animate', '$q', '$parse', '$injector', function ($animate, $q, $parse, $injector) {
  	var $animateCss = $injector.has('$animateCss') ? $injector.get('$animateCss') : null;
  	return {
  		link: function (scope, element, attrs) {
  			var expandingExpr = $parse(attrs.expanding),
			  expandedExpr = $parse(attrs.expanded),
			  collapsingExpr = $parse(attrs.collapsing),
			  collapsedExpr = $parse(attrs.collapsed),
			  horizontal = false,
			  css = {},
			  cssTo = {};

  			init();

  			function init() {
  				horizontal = !!('horizontal' in attrs);
  				if (horizontal) {
  					css = {
  						width: ''
  					};
  					cssTo = { width: '0' };
  				} else {
  					css = {
  						height: ''
  					};
  					cssTo = { height: '0' };
  				}
  				if (!scope.$eval(attrs.uibCollapse)) {
  					element.addClass('in')
					  .addClass('collapse')
					  .attr('aria-expanded', true)
					  .attr('aria-hidden', false)
					  .css(css);
  				}
  			}

  			function getScrollFromElement(element) {
  				if (horizontal) {
  					return { width: element.scrollWidth + 'px' };
  				}
  				return { height: element.scrollHeight + 'px' };
  			}

  			function expand() {
  				if (element.hasClass('collapse') && element.hasClass('in')) {
  					return;
  				}

  				$q.resolve(expandingExpr(scope))
				  .then(function () {
				  	element.removeClass('collapse')
					  .addClass('collapsing')
					  .attr('aria-expanded', true)
					  .attr('aria-hidden', false);

				  	if ($animateCss) {
				  		$animateCss(element, {
				  			addClass: 'in',
				  			easing: 'ease',
				  			css: {
				  				overflow: 'hidden'
				  			},
				  			to: getScrollFromElement(element[0])
				  		}).start()['finally'](expandDone);
				  	} else {
				  		$animate.addClass(element, 'in', {
				  			css: {
				  				overflow: 'hidden'
				  			},
				  			to: getScrollFromElement(element[0])
				  		}).then(expandDone);
				  	}
				  });
  			}

  			function expandDone() {
  				element.removeClass('collapsing')
				  .addClass('collapse')
				  .css(css);
  				expandedExpr(scope);
  			}

  			function collapse() {
  				if (!element.hasClass('collapse') && !element.hasClass('in')) {
  					return collapseDone();
  				}

  				$q.resolve(collapsingExpr(scope))
				  .then(function () {
				  	element
					// IMPORTANT: The width must be set before adding "collapsing" class.
					// Otherwise, the browser attempts to animate from width 0 (in
					// collapsing class) to the given width here.
					  .css(getScrollFromElement(element[0]))
					  // initially all panel collapse have the collapse class, this removal
					  // prevents the animation from jumping to collapsed state
					  .removeClass('collapse')
					  .addClass('collapsing')
					  .attr('aria-expanded', false)
					  .attr('aria-hidden', true);

				  	if ($animateCss) {
				  		$animateCss(element, {
				  			removeClass: 'in',
				  			to: cssTo
				  		}).start()['finally'](collapseDone);
				  	} else {
				  		$animate.removeClass(element, 'in', {
				  			to: cssTo
				  		}).then(collapseDone);
				  	}
				  });
  			}

  			function collapseDone() {
  				element.css(cssTo); // Required so that collapse works when animation is disabled
  				element.removeClass('collapsing')
				  .addClass('collapse');
  				collapsedExpr(scope);
  			}

  			scope.$watch(attrs.uibCollapse, function (shouldCollapse) {
  				if (shouldCollapse) {
  					collapse();
  				} else {
  					expand();
  				}
  			});
  		}
  	};
  }]);

angular.module('ui.bootstrap.tabindex', [])

.directive('uibTabindexToggle', function () {
	return {
		restrict: 'A',
		link: function (scope, elem, attrs) {
			attrs.$observe('disabled', function (disabled) {
				attrs.$set('tabindex', disabled ? -1 : null);
			});
		}
	};
});

angular.module('ui.bootstrap.accordion', ['ui.bootstrap.collapse', 'ui.bootstrap.tabindex'])

.constant('uibAccordionConfig', {
	closeOthers: true
})

.controller('UibAccordionController', ['$scope', '$attrs', 'uibAccordionConfig', function ($scope, $attrs, accordionConfig) {
	// This array keeps track of the accordion groups
	this.groups = [];

	// Ensure that all the groups in this accordion are closed, unless close-others explicitly says not to
	this.closeOthers = function (openGroup) {
		var closeOthers = angular.isDefined($attrs.closeOthers) ?
		  $scope.$eval($attrs.closeOthers) : accordionConfig.closeOthers;
		if (closeOthers) {
			angular.forEach(this.groups, function (group) {
				if (group !== openGroup) {
					group.isOpen = false;
				}
			});
		}
	};

	// This is called from the accordion-group directive to add itself to the accordion
	this.addGroup = function (groupScope) {
		var that = this;
		this.groups.push(groupScope);

		groupScope.$on('$destroy', function (event) {
			that.removeGroup(groupScope);
		});
	};

	// This is called from the accordion-group directive when to remove itself
	this.removeGroup = function (group) {
		var index = this.groups.indexOf(group);
		if (index !== -1) {
			this.groups.splice(index, 1);
		}
	};
}])

// The accordion directive simply sets up the directive controller
// and adds an accordion CSS class to itself element.
.directive('uibAccordion', function () {
	return {
		controller: 'UibAccordionController',
		controllerAs: 'accordion',
		transclude: true,
		templateUrl: function (element, attrs) {
			return attrs.templateUrl || 'uib/template/accordion/accordion.html';
		}
	};
})

// The accordion-group directive indicates a block of html that will expand and collapse in an accordion
.directive('uibAccordionGroup', function () {
	return {
		require: '^uibAccordion',         // We need this directive to be inside an accordion
		transclude: true,              // It transcludes the contents of the directive into the template
		restrict: 'A',
		templateUrl: function (element, attrs) {
			return attrs.templateUrl || 'uib/template/accordion/accordion-group.html';
		},
		scope: {
			heading: '@',               // Interpolate the heading attribute onto this scope
			panelClass: '@?',           // Ditto with panelClass
			isOpen: '=?',
			isDisabled: '=?'
		},
		controller: function () {
			this.setHeading = function (element) {
				this.heading = element;
			};
		},
		link: function (scope, element, attrs, accordionCtrl) {
			element.addClass('panel');
			accordionCtrl.addGroup(scope);

			scope.openClass = attrs.openClass || 'panel-open';
			scope.panelClass = attrs.panelClass || 'panel-default';
			scope.$watch('isOpen', function (value) {
				element.toggleClass(scope.openClass, !!value);
				if (value) {
					accordionCtrl.closeOthers(scope);
				}
			});

			scope.toggleOpen = function ($event) {
				if (!scope.isDisabled) {
					if (!$event || $event.which === 32) {
						scope.isOpen = !scope.isOpen;
					}
				}
			};

			var id = 'accordiongroup-' + scope.$id + '-' + Math.floor(Math.random() * 10000);
			scope.headingId = id + '-tab';
			scope.panelId = id + '-panel';
		}
	};
})

// Use accordion-heading below an accordion-group to provide a heading containing HTML
.directive('uibAccordionHeading', function () {
	return {
		transclude: true,   // Grab the contents to be used as the heading
		template: '',       // In effect remove this element!
		replace: true,
		require: '^uibAccordionGroup',
		link: function (scope, element, attrs, accordionGroupCtrl, transclude) {
			// Pass the heading to the accordion-group controller
			// so that it can be transcluded into the right place in the template
			// [The second parameter to transclude causes the elements to be cloned so that they work in ng-repeat]
			accordionGroupCtrl.setHeading(transclude(scope, angular.noop));
		}
	};
})

// Use in the accordion-group template to indicate where you want the heading to be transcluded
// You must provide the property on the accordion-group controller that will hold the transcluded element
.directive('uibAccordionTransclude', function () {
	return {
		require: '^uibAccordionGroup',
		link: function (scope, element, attrs, controller) {
			scope.$watch(function () { return controller[attrs.uibAccordionTransclude]; }, function (heading) {
				if (heading) {
					var elem = angular.element(element[0].querySelector(getHeaderSelectors()));
					elem.html('');
					elem.append(heading);
				}
			});
		}
	};

	function getHeaderSelectors() {
		return 'uib-accordion-header,' +
			'data-uib-accordion-header,' +
			'x-uib-accordion-header,' +
			'uib\\:accordion-header,' +
			'[uib-accordion-header],' +
			'[data-uib-accordion-header],' +
			'[x-uib-accordion-header]';
	}
});

angular.module('ui.bootstrap.alert', [])

.controller('UibAlertController', ['$scope', '$element', '$attrs', '$interpolate', '$timeout', function ($scope, $element, $attrs, $interpolate, $timeout) {
	$scope.closeable = !!$attrs.close;
	$element.addClass('alert');
	$attrs.$set('role', 'alert');
	if ($scope.closeable) {
		$element.addClass('alert-dismissible');
	}

	var dismissOnTimeout = angular.isDefined($attrs.dismissOnTimeout) ?
	  $interpolate($attrs.dismissOnTimeout)($scope.$parent) : null;

	if (dismissOnTimeout) {
		$timeout(function () {
			$scope.close();
		}, parseInt(dismissOnTimeout, 10));
	}
}])

.directive('uibAlert', function () {
	return {
		controller: 'UibAlertController',
		controllerAs: 'alert',
		restrict: 'A',
		templateUrl: function (element, attrs) {
			return attrs.templateUrl || 'uib/template/alert/alert.html';
		},
		transclude: true,
		scope: {
			close: '&'
		}
	};
});

angular.module('ui.bootstrap.buttons', [])

.constant('uibButtonConfig', {
	activeClass: 'active',
	toggleEvent: 'click'
})

.controller('UibButtonsController', ['uibButtonConfig', function (buttonConfig) {
	this.activeClass = buttonConfig.activeClass || 'active';
	this.toggleEvent = buttonConfig.toggleEvent || 'click';
}])

.directive('uibBtnRadio', ['$parse', function ($parse) {
	return {
		require: ['uibBtnRadio', 'ngModel'],
		controller: 'UibButtonsController',
		controllerAs: 'buttons',
		link: function (scope, element, attrs, ctrls) {
			var buttonsCtrl = ctrls[0], ngModelCtrl = ctrls[1];
			var uncheckableExpr = $parse(attrs.uibUncheckable);

			element.find('input').css({ display: 'none' });

			//model -> UI
			ngModelCtrl.$render = function () {
				element.toggleClass(buttonsCtrl.activeClass, angular.equals(ngModelCtrl.$modelValue, scope.$eval(attrs.uibBtnRadio)));
			};

			//ui->model
			element.on(buttonsCtrl.toggleEvent, function () {
				if (attrs.disabled) {
					return;
				}

				var isActive = element.hasClass(buttonsCtrl.activeClass);

				if (!isActive || angular.isDefined(attrs.uncheckable)) {
					scope.$apply(function () {
						ngModelCtrl.$setViewValue(isActive ? null : scope.$eval(attrs.uibBtnRadio));
						ngModelCtrl.$render();
					});
				}
			});

			if (attrs.uibUncheckable) {
				scope.$watch(uncheckableExpr, function (uncheckable) {
					attrs.$set('uncheckable', uncheckable ? '' : undefined);
				});
			}
		}
	};
}])

.directive('uibBtnCheckbox', function () {
	return {
		require: ['uibBtnCheckbox', 'ngModel'],
		controller: 'UibButtonsController',
		controllerAs: 'button',
		link: function (scope, element, attrs, ctrls) {
			var buttonsCtrl = ctrls[0], ngModelCtrl = ctrls[1];

			element.find('input').css({ display: 'none' });

			function getTrueValue() {
				return getCheckboxValue(attrs.btnCheckboxTrue, true);
			}

			function getFalseValue() {
				return getCheckboxValue(attrs.btnCheckboxFalse, false);
			}

			function getCheckboxValue(attribute, defaultValue) {
				return angular.isDefined(attribute) ? scope.$eval(attribute) : defaultValue;
			}

			//model -> UI
			ngModelCtrl.$render = function () {
				element.toggleClass(buttonsCtrl.activeClass, angular.equals(ngModelCtrl.$modelValue, getTrueValue()));
			};

			//ui->model
			element.on(buttonsCtrl.toggleEvent, function () {
				if (attrs.disabled) {
					return;
				}

				scope.$apply(function () {
					ngModelCtrl.$setViewValue(element.hasClass(buttonsCtrl.activeClass) ? getFalseValue() : getTrueValue());
					ngModelCtrl.$render();
				});
			});
		}
	};
});

angular.module('ui.bootstrap.carousel', [])

.controller('UibCarouselController', ['$scope', '$element', '$interval', '$timeout', '$animate', function ($scope, $element, $interval, $timeout, $animate) {
	var self = this,
	  slides = self.slides = $scope.slides = [],
	  SLIDE_DIRECTION = 'uib-slideDirection',
	  currentIndex = $scope.active,
	  currentInterval, isPlaying, bufferedTransitions = [];

	var destroyed = false;
	$element.addClass('carousel');

	self.addSlide = function (slide, element) {
		slides.push({
			slide: slide,
			element: element
		});
		slides.sort(function (a, b) {
			return +a.slide.index - +b.slide.index;
		});
		//if this is the first slide or the slide is set to active, select it
		if (slide.index === $scope.active || slides.length === 1 && !angular.isNumber($scope.active)) {
			if ($scope.$currentTransition) {
				$scope.$currentTransition = null;
			}

			currentIndex = slide.index;
			$scope.active = slide.index;
			setActive(currentIndex);
			self.select(slides[findSlideIndex(slide)]);
			if (slides.length === 1) {
				$scope.play();
			}
		}
	};

	self.getCurrentIndex = function () {
		for (var i = 0; i < slides.length; i++) {
			if (slides[i].slide.index === currentIndex) {
				return i;
			}
		}
	};

	self.next = $scope.next = function () {
		var newIndex = (self.getCurrentIndex() + 1) % slides.length;

		if (newIndex === 0 && $scope.noWrap()) {
			$scope.pause();
			return;
		}

		return self.select(slides[newIndex], 'next');
	};

	self.prev = $scope.prev = function () {
		var newIndex = self.getCurrentIndex() - 1 < 0 ? slides.length - 1 : self.getCurrentIndex() - 1;

		if ($scope.noWrap() && newIndex === slides.length - 1) {
			$scope.pause();
			return;
		}

		return self.select(slides[newIndex], 'prev');
	};

	self.removeSlide = function (slide) {
		var index = findSlideIndex(slide);

		var bufferedIndex = bufferedTransitions.indexOf(slides[index]);
		if (bufferedIndex !== -1) {
			bufferedTransitions.splice(bufferedIndex, 1);
		}

		//get the index of the slide inside the carousel
		slides.splice(index, 1);
		if (slides.length > 0 && currentIndex === index) {
			if (index >= slides.length) {
				currentIndex = slides.length - 1;
				$scope.active = currentIndex;
				setActive(currentIndex);
				self.select(slides[slides.length - 1]);
			} else {
				currentIndex = index;
				$scope.active = currentIndex;
				setActive(currentIndex);
				self.select(slides[index]);
			}
		} else if (currentIndex > index) {
			currentIndex--;
			$scope.active = currentIndex;
		}

		//clean the active value when no more slide
		if (slides.length === 0) {
			currentIndex = null;
			$scope.active = null;
			clearBufferedTransitions();
		}
	};

	/* direction: "prev" or "next" */
	self.select = $scope.select = function (nextSlide, direction) {
		var nextIndex = findSlideIndex(nextSlide.slide);
		//Decide direction if it's not given
		if (direction === undefined) {
			direction = nextIndex > self.getCurrentIndex() ? 'next' : 'prev';
		}
		//Prevent this user-triggered transition from occurring if there is already one in progress
		if (nextSlide.slide.index !== currentIndex &&
		  !$scope.$currentTransition) {
			goNext(nextSlide.slide, nextIndex, direction);
		} else if (nextSlide && nextSlide.slide.index !== currentIndex && $scope.$currentTransition) {
			bufferedTransitions.push(slides[nextIndex]);
		}
	};

	/* Allow outside people to call indexOf on slides array */
	$scope.indexOfSlide = function (slide) {
		return +slide.slide.index;
	};

	$scope.isActive = function (slide) {
		return $scope.active === slide.slide.index;
	};

	$scope.isPrevDisabled = function () {
		return $scope.active === 0 && $scope.noWrap();
	};

	$scope.isNextDisabled = function () {
		return $scope.active === slides.length - 1 && $scope.noWrap();
	};

	$scope.pause = function () {
		if (!$scope.noPause) {
			isPlaying = false;
			resetTimer();
		}
	};

	$scope.play = function () {
		if (!isPlaying) {
			isPlaying = true;
			restartTimer();
		}
	};

	$element.on('mouseenter', $scope.pause);
	$element.on('mouseleave', $scope.play);

	$scope.$on('$destroy', function () {
		destroyed = true;
		resetTimer();
	});

	$scope.$watch('noTransition', function (noTransition) {
		$animate.enabled($element, !noTransition);
	});

	$scope.$watch('interval', restartTimer);

	$scope.$watchCollection('slides', resetTransition);

	$scope.$watch('active', function (index) {
		if (angular.isNumber(index) && currentIndex !== index) {
			for (var i = 0; i < slides.length; i++) {
				if (slides[i].slide.index === index) {
					index = i;
					break;
				}
			}

			var slide = slides[index];
			if (slide) {
				setActive(index);
				self.select(slides[index]);
				currentIndex = index;
			}
		}
	});

	function clearBufferedTransitions() {
		while (bufferedTransitions.length) {
			bufferedTransitions.shift();
		}
	}

	function getSlideByIndex(index) {
		for (var i = 0, l = slides.length; i < l; ++i) {
			if (slides[i].index === index) {
				return slides[i];
			}
		}
	}

	function setActive(index) {
		for (var i = 0; i < slides.length; i++) {
			slides[i].slide.active = i === index;
		}
	}

	function goNext(slide, index, direction) {
		if (destroyed) {
			return;
		}

		angular.extend(slide, { direction: direction });
		angular.extend(slides[currentIndex].slide || {}, { direction: direction });
		if ($animate.enabled($element) && !$scope.$currentTransition &&
		  slides[index].element && self.slides.length > 1) {
			slides[index].element.data(SLIDE_DIRECTION, slide.direction);
			var currentIdx = self.getCurrentIndex();

			if (angular.isNumber(currentIdx) && slides[currentIdx].element) {
				slides[currentIdx].element.data(SLIDE_DIRECTION, slide.direction);
			}

			$scope.$currentTransition = true;
			$animate.on('addClass', slides[index].element, function (element, phase) {
				if (phase === 'close') {
					$scope.$currentTransition = null;
					$animate.off('addClass', element);
					if (bufferedTransitions.length) {
						var nextSlide = bufferedTransitions.pop().slide;
						var nextIndex = nextSlide.index;
						var nextDirection = nextIndex > self.getCurrentIndex() ? 'next' : 'prev';
						clearBufferedTransitions();

						goNext(nextSlide, nextIndex, nextDirection);
					}
				}
			});
		}

		$scope.active = slide.index;
		currentIndex = slide.index;
		setActive(index);

		//every time you change slides, reset the timer
		restartTimer();
	}

	function findSlideIndex(slide) {
		for (var i = 0; i < slides.length; i++) {
			if (slides[i].slide === slide) {
				return i;
			}
		}
	}

	function resetTimer() {
		if (currentInterval) {
			$interval.cancel(currentInterval);
			currentInterval = null;
		}
	}

	function resetTransition(slides) {
		if (!slides.length) {
			$scope.$currentTransition = null;
			clearBufferedTransitions();
		}
	}

	function restartTimer() {
		resetTimer();
		var interval = +$scope.interval;
		if (!isNaN(interval) && interval > 0) {
			currentInterval = $interval(timerFn, interval);
		}
	}

	function timerFn() {
		var interval = +$scope.interval;
		if (isPlaying && !isNaN(interval) && interval > 0 && slides.length) {
			$scope.next();
		} else {
			$scope.pause();
		}
	}
}])

.directive('uibCarousel', function () {
	return {
		transclude: true,
		controller: 'UibCarouselController',
		controllerAs: 'carousel',
		restrict: 'A',
		templateUrl: function (element, attrs) {
			return attrs.templateUrl || 'uib/template/carousel/carousel.html';
		},
		scope: {
			active: '=',
			interval: '=',
			noTransition: '=',
			noPause: '=',
			noWrap: '&'
		}
	};
})

.directive('uibSlide', ['$animate', function ($animate) {
	return {
		require: '^uibCarousel',
		restrict: 'A',
		transclude: true,
		templateUrl: function (element, attrs) {
			return attrs.templateUrl || 'uib/template/carousel/slide.html';
		},
		scope: {
			actual: '=?',
			index: '=?'
		},
		link: function (scope, element, attrs, carouselCtrl) {
			element.addClass('item');
			carouselCtrl.addSlide(scope, element);
			//when the scope is destroyed then remove the slide from the current slides array
			scope.$on('$destroy', function () {
				carouselCtrl.removeSlide(scope);
			});

			scope.$watch('active', function (active) {
				$animate[active ? 'addClass' : 'removeClass'](element, 'active');
			});
		}
	};
}])

.animation('.item', ['$animateCss',
function ($animateCss) {
	var SLIDE_DIRECTION = 'uib-slideDirection';

	function removeClass(element, className, callback) {
		element.removeClass(className);
		if (callback) {
			callback();
		}
	}

	return {
		beforeAddClass: function (element, className, done) {
			if (className === 'active') {
				var stopped = false;
				var direction = element.data(SLIDE_DIRECTION);
				var directionClass = direction === 'next' ? 'left' : 'right';
				var removeClassFn = removeClass.bind(this, element,
				  directionClass + ' ' + direction, done);
				element.addClass(direction);

				$animateCss(element, { addClass: directionClass })
				  .start()
				  .done(removeClassFn);

				return function () {
					stopped = true;
				};
			}
			done();
		},
		beforeRemoveClass: function (element, className, done) {
			if (className === 'active') {
				var stopped = false;
				var direction = element.data(SLIDE_DIRECTION);
				var directionClass = direction === 'next' ? 'left' : 'right';
				var removeClassFn = removeClass.bind(this, element, directionClass, done);

				$animateCss(element, { addClass: directionClass })
				  .start()
				  .done(removeClassFn);

				return function () {
					stopped = true;
				};
			}
			done();
		}
	};
}]);

angular.module('ui.bootstrap.dateparser', [])

.service('uibDateParser', ['$log', '$locale', 'dateFilter', 'orderByFilter', function ($log, $locale, dateFilter, orderByFilter) {
	// Pulled from https://github.com/mbostock/d3/blob/master/src/format/requote.js
	var SPECIAL_CHARACTERS_REGEXP = /[\\\^\$\*\+\?\|\[\]\(\)\.\{\}]/g;

	var localeId;
	var formatCodeToRegex;

	this.init = function () {
		localeId = $locale.id;

		this.parsers = {};
		this.formatters = {};

		formatCodeToRegex = [
		  {
		  	key: 'yyyy',
		  	regex: '\\d{4}',
		  	apply: function (value) { this.year = +value; },
		  	formatter: function (date) {
		  		var _date = new Date();
		  		_date.setFullYear(Math.abs(date.getFullYear()));
		  		return dateFilter(_date, 'yyyy');
		  	}
		  },
		  {
		  	key: 'yy',
		  	regex: '\\d{2}',
		  	apply: function (value) { value = +value; this.year = value < 69 ? value + 2000 : value + 1900; },
		  	formatter: function (date) {
		  		var _date = new Date();
		  		_date.setFullYear(Math.abs(date.getFullYear()));
		  		return dateFilter(_date, 'yy');
		  	}
		  },
		  {
		  	key: 'y',
		  	regex: '\\d{1,4}',
		  	apply: function (value) { this.year = +value; },
		  	formatter: function (date) {
		  		var _date = new Date();
		  		_date.setFullYear(Math.abs(date.getFullYear()));
		  		return dateFilter(_date, 'y');
		  	}
		  },
		  {
		  	key: 'M!',
		  	regex: '0?[1-9]|1[0-2]',
		  	apply: function (value) { this.month = value - 1; },
		  	formatter: function (date) {
		  		var value = date.getMonth();
		  		if (/^[0-9]$/.test(value)) {
		  			return dateFilter(date, 'MM');
		  		}

		  		return dateFilter(date, 'M');
		  	}
		  },
		  {
		  	key: 'MMMM',
		  	regex: $locale.DATETIME_FORMATS.MONTH.join('|'),
		  	apply: function (value) { this.month = $locale.DATETIME_FORMATS.MONTH.indexOf(value); },
		  	formatter: function (date) { return dateFilter(date, 'MMMM'); }
		  },
		  {
		  	key: 'MMM',
		  	regex: $locale.DATETIME_FORMATS.SHORTMONTH.join('|'),
		  	apply: function (value) { this.month = $locale.DATETIME_FORMATS.SHORTMONTH.indexOf(value); },
		  	formatter: function (date) { return dateFilter(date, 'MMM'); }
		  },
		  {
		  	key: 'MM',
		  	regex: '0[1-9]|1[0-2]',
		  	apply: function (value) { this.month = value - 1; },
		  	formatter: function (date) { return dateFilter(date, 'MM'); }
		  },
		  {
		  	key: 'M',
		  	regex: '[1-9]|1[0-2]',
		  	apply: function (value) { this.month = value - 1; },
		  	formatter: function (date) { return dateFilter(date, 'M'); }
		  },
		  {
		  	key: 'd!',
		  	regex: '[0-2]?[0-9]{1}|3[0-1]{1}',
		  	apply: function (value) { this.date = +value; },
		  	formatter: function (date) {
		  		var value = date.getDate();
		  		if (/^[1-9]$/.test(value)) {
		  			return dateFilter(date, 'dd');
		  		}

		  		return dateFilter(date, 'd');
		  	}
		  },
		  {
		  	key: 'dd',
		  	regex: '[0-2][0-9]{1}|3[0-1]{1}',
		  	apply: function (value) { this.date = +value; },
		  	formatter: function (date) { return dateFilter(date, 'dd'); }
		  },
		  {
		  	key: 'd',
		  	regex: '[1-2]?[0-9]{1}|3[0-1]{1}',
		  	apply: function (value) { this.date = +value; },
		  	formatter: function (date) { return dateFilter(date, 'd'); }
		  },
		  {
		  	key: 'EEEE',
		  	regex: $locale.DATETIME_FORMATS.DAY.join('|'),
		  	formatter: function (date) { return dateFilter(date, 'EEEE'); }
		  },
		  {
		  	key: 'EEE',
		  	regex: $locale.DATETIME_FORMATS.SHORTDAY.join('|'),
		  	formatter: function (date) { return dateFilter(date, 'EEE'); }
		  },
		  {
		  	key: 'HH',
		  	regex: '(?:0|1)[0-9]|2[0-3]',
		  	apply: function (value) { this.hours = +value; },
		  	formatter: function (date) { return dateFilter(date, 'HH'); }
		  },
		  {
		  	key: 'hh',
		  	regex: '0[0-9]|1[0-2]',
		  	apply: function (value) { this.hours = +value; },
		  	formatter: function (date) { return dateFilter(date, 'hh'); }
		  },
		  {
		  	key: 'H',
		  	regex: '1?[0-9]|2[0-3]',
		  	apply: function (value) { this.hours = +value; },
		  	formatter: function (date) { return dateFilter(date, 'H'); }
		  },
		  {
		  	key: 'h',
		  	regex: '[0-9]|1[0-2]',
		  	apply: function (value) { this.hours = +value; },
		  	formatter: function (date) { return dateFilter(date, 'h'); }
		  },
		  {
		  	key: 'mm',
		  	regex: '[0-5][0-9]',
		  	apply: function (value) { this.minutes = +value; },
		  	formatter: function (date) { return dateFilter(date, 'mm'); }
		  },
		  {
		  	key: 'm',
		  	regex: '[0-9]|[1-5][0-9]',
		  	apply: function (value) { this.minutes = +value; },
		  	formatter: function (date) { return dateFilter(date, 'm'); }
		  },
		  {
		  	key: 'sss',
		  	regex: '[0-9][0-9][0-9]',
		  	apply: function (value) { this.milliseconds = +value; },
		  	formatter: function (date) { return dateFilter(date, 'sss'); }
		  },
		  {
		  	key: 'ss',
		  	regex: '[0-5][0-9]',
		  	apply: function (value) { this.seconds = +value; },
		  	formatter: function (date) { return dateFilter(date, 'ss'); }
		  },
		  {
		  	key: 's',
		  	regex: '[0-9]|[1-5][0-9]',
		  	apply: function (value) { this.seconds = +value; },
		  	formatter: function (date) { return dateFilter(date, 's'); }
		  },
		  {
		  	key: 'a',
		  	regex: $locale.DATETIME_FORMATS.AMPMS.join('|'),
		  	apply: function (value) {
		  		if (this.hours === 12) {
		  			this.hours = 0;
		  		}

		  		if (value === 'PM') {
		  			this.hours += 12;
		  		}
		  	},
		  	formatter: function (date) { return dateFilter(date, 'a'); }
		  },
		  {
		  	key: 'Z',
		  	regex: '[+-]\\d{4}',
		  	apply: function (value) {
		  		var matches = value.match(/([+-])(\d{2})(\d{2})/),
				  sign = matches[1],
				  hours = matches[2],
				  minutes = matches[3];
		  		this.hours += toInt(sign + hours);
		  		this.minutes += toInt(sign + minutes);
		  	},
		  	formatter: function (date) {
		  		return dateFilter(date, 'Z');
		  	}
		  },
		  {
		  	key: 'ww',
		  	regex: '[0-4][0-9]|5[0-3]',
		  	formatter: function (date) { return dateFilter(date, 'ww'); }
		  },
		  {
		  	key: 'w',
		  	regex: '[0-9]|[1-4][0-9]|5[0-3]',
		  	formatter: function (date) { return dateFilter(date, 'w'); }
		  },
		  {
		  	key: 'GGGG',
		  	regex: $locale.DATETIME_FORMATS.ERANAMES.join('|').replace(/\s/g, '\\s'),
		  	formatter: function (date) { return dateFilter(date, 'GGGG'); }
		  },
		  {
		  	key: 'GGG',
		  	regex: $locale.DATETIME_FORMATS.ERAS.join('|'),
		  	formatter: function (date) { return dateFilter(date, 'GGG'); }
		  },
		  {
		  	key: 'GG',
		  	regex: $locale.DATETIME_FORMATS.ERAS.join('|'),
		  	formatter: function (date) { return dateFilter(date, 'GG'); }
		  },
		  {
		  	key: 'G',
		  	regex: $locale.DATETIME_FORMATS.ERAS.join('|'),
		  	formatter: function (date) { return dateFilter(date, 'G'); }
		  }
		];
	};

	this.init();

	function createParser(format) {
		var map = [], regex = format.split('');

		// check for literal values
		var quoteIndex = format.indexOf('\'');
		if (quoteIndex > -1) {
			var inLiteral = false;
			format = format.split('');
			for (var i = quoteIndex; i < format.length; i++) {
				if (inLiteral) {
					if (format[i] === '\'') {
						if (i + 1 < format.length && format[i + 1] === '\'') { // escaped single quote
							format[i + 1] = '$';
							regex[i + 1] = '';
						} else { // end of literal
							regex[i] = '';
							inLiteral = false;
						}
					}
					format[i] = '$';
				} else {
					if (format[i] === '\'') { // start of literal
						format[i] = '$';
						regex[i] = '';
						inLiteral = true;
					}
				}
			}

			format = format.join('');
		}

		angular.forEach(formatCodeToRegex, function (data) {
			var index = format.indexOf(data.key);

			if (index > -1) {
				format = format.split('');

				regex[index] = '(' + data.regex + ')';
				format[index] = '$'; // Custom symbol to define consumed part of format
				for (var i = index + 1, n = index + data.key.length; i < n; i++) {
					regex[i] = '';
					format[i] = '$';
				}
				format = format.join('');

				map.push({
					index: index,
					key: data.key,
					apply: data.apply,
					matcher: data.regex
				});
			}
		});

		return {
			regex: new RegExp('^' + regex.join('') + '$'),
			map: orderByFilter(map, 'index')
		};
	}

	function createFormatter(format) {
		var formatters = [];
		var i = 0;
		var formatter, literalIdx;
		while (i < format.length) {
			if (angular.isNumber(literalIdx)) {
				if (format.charAt(i) === '\'') {
					if (i + 1 >= format.length || format.charAt(i + 1) !== '\'') {
						formatters.push(constructLiteralFormatter(format, literalIdx, i));
						literalIdx = null;
					}
				} else if (i === format.length) {
					while (literalIdx < format.length) {
						formatter = constructFormatterFromIdx(format, literalIdx);
						formatters.push(formatter);
						literalIdx = formatter.endIdx;
					}
				}

				i++;
				continue;
			}

			if (format.charAt(i) === '\'') {
				literalIdx = i;
				i++;
				continue;
			}

			formatter = constructFormatterFromIdx(format, i);

			formatters.push(formatter.parser);
			i = formatter.endIdx;
		}

		return formatters;
	}

	function constructLiteralFormatter(format, literalIdx, endIdx) {
		return function () {
			return format.substr(literalIdx + 1, endIdx - literalIdx - 1);
		};
	}

	function constructFormatterFromIdx(format, i) {
		var currentPosStr = format.substr(i);
		for (var j = 0; j < formatCodeToRegex.length; j++) {
			if (new RegExp('^' + formatCodeToRegex[j].key).test(currentPosStr)) {
				var data = formatCodeToRegex[j];
				return {
					endIdx: i + data.key.length,
					parser: data.formatter
				};
			}
		}

		return {
			endIdx: i + 1,
			parser: function () {
				return currentPosStr.charAt(0);
			}
		};
	}

	this.filter = function (date, format) {
		if (!angular.isDate(date) || isNaN(date) || !format) {
			return '';
		}

		format = $locale.DATETIME_FORMATS[format] || format;

		if ($locale.id !== localeId) {
			this.init();
		}

		if (!this.formatters[format]) {
			this.formatters[format] = createFormatter(format);
		}

		var formatters = this.formatters[format];

		return formatters.reduce(function (str, formatter) {
			return str + formatter(date);
		}, '');
	};

	this.parse = function (input, format, baseDate) {
		if (!angular.isString(input) || !format) {
			return input;
		}

		format = $locale.DATETIME_FORMATS[format] || format;
		format = format.replace(SPECIAL_CHARACTERS_REGEXP, '\\$&');

		if ($locale.id !== localeId) {
			this.init();
		}

		if (!this.parsers[format]) {
			this.parsers[format] = createParser(format, 'apply');
		}

		var parser = this.parsers[format],
			regex = parser.regex,
			map = parser.map,
			results = input.match(regex),
			tzOffset = false;
		if (results && results.length) {
			var fields, dt;
			if (angular.isDate(baseDate) && !isNaN(baseDate.getTime())) {
				fields = {
					year: baseDate.getFullYear(),
					month: baseDate.getMonth(),
					date: baseDate.getDate(),
					hours: baseDate.getHours(),
					minutes: baseDate.getMinutes(),
					seconds: baseDate.getSeconds(),
					milliseconds: baseDate.getMilliseconds()
				};
			} else {
				if (baseDate) {
					$log.warn('dateparser:', 'baseDate is not a valid date');
				}
				fields = { year: 1900, month: 0, date: 1, hours: 0, minutes: 0, seconds: 0, milliseconds: 0 };
			}

			for (var i = 1, n = results.length; i < n; i++) {
				var mapper = map[i - 1];
				if (mapper.matcher === 'Z') {
					tzOffset = true;
				}

				if (mapper.apply) {
					mapper.apply.call(fields, results[i]);
				}
			}

			var datesetter = tzOffset ? Date.prototype.setUTCFullYear :
			  Date.prototype.setFullYear;
			var timesetter = tzOffset ? Date.prototype.setUTCHours :
			  Date.prototype.setHours;

			if (isValid(fields.year, fields.month, fields.date)) {
				if (angular.isDate(baseDate) && !isNaN(baseDate.getTime()) && !tzOffset) {
					dt = new Date(baseDate);
					datesetter.call(dt, fields.year, fields.month, fields.date);
					timesetter.call(dt, fields.hours, fields.minutes,
					  fields.seconds, fields.milliseconds);
				} else {
					dt = new Date(0);
					datesetter.call(dt, fields.year, fields.month, fields.date);
					timesetter.call(dt, fields.hours || 0, fields.minutes || 0,
					  fields.seconds || 0, fields.milliseconds || 0);
				}
			}

			return dt;
		}
	};

	// Check if date is valid for specific month (and year for February).
	// Month: 0 = Jan, 1 = Feb, etc
	function isValid(year, month, date) {
		if (date < 1) {
			return false;
		}

		if (month === 1 && date > 28) {
			return date === 29 && (year % 4 === 0 && year % 100 !== 0 || year % 400 === 0);
		}

		if (month === 3 || month === 5 || month === 8 || month === 10) {
			return date < 31;
		}

		return true;
	}

	function toInt(str) {
		return parseInt(str, 10);
	}

	this.toTimezone = toTimezone;
	this.fromTimezone = fromTimezone;
	this.timezoneToOffset = timezoneToOffset;
	this.addDateMinutes = addDateMinutes;
	this.convertTimezoneToLocal = convertTimezoneToLocal;

	function toTimezone(date, timezone) {
		return date && timezone ? convertTimezoneToLocal(date, timezone) : date;
	}

	function fromTimezone(date, timezone) {
		return date && timezone ? convertTimezoneToLocal(date, timezone, true) : date;
	}

	//https://github.com/angular/angular.js/blob/622c42169699ec07fc6daaa19fe6d224e5d2f70e/src/Angular.js#L1207
	function timezoneToOffset(timezone, fallback) {
		timezone = timezone.replace(/:/g, '');
		var requestedTimezoneOffset = Date.parse('Jan 01, 1970 00:00:00 ' + timezone) / 60000;
		return isNaN(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));
	}
}]);

// Avoiding use of ng-class as it creates a lot of watchers when a class is to be applied to
// at most one element.
angular.module('ui.bootstrap.isClass', [])
.directive('uibIsClass', [
         '$animate',
function ($animate) {
	//                    11111111          22222222
	var ON_REGEXP = /^\s*([\s\S]+?)\s+on\s+([\s\S]+?)\s*$/;
	//                    11111111           22222222
	var IS_REGEXP = /^\s*([\s\S]+?)\s+for\s+([\s\S]+?)\s*$/;

	var dataPerTracked = {};

	return {
		restrict: 'A',
		compile: function (tElement, tAttrs) {
			var linkedScopes = [];
			var instances = [];
			var expToData = {};
			var lastActivated = null;
			var onExpMatches = tAttrs.uibIsClass.match(ON_REGEXP);
			var onExp = onExpMatches[2];
			var expsStr = onExpMatches[1];
			var exps = expsStr.split(',');

			return linkFn;

			function linkFn(scope, element, attrs) {
				linkedScopes.push(scope);
				instances.push({
					scope: scope,
					element: element
				});

				exps.forEach(function (exp, k) {
					addForExp(exp, scope);
				});

				scope.$on('$destroy', removeScope);
			}

			function addForExp(exp, scope) {
				var matches = exp.match(IS_REGEXP);
				var clazz = scope.$eval(matches[1]);
				var compareWithExp = matches[2];
				var data = expToData[exp];
				if (!data) {
					var watchFn = function (compareWithVal) {
						var newActivated = null;
						instances.some(function (instance) {
							var thisVal = instance.scope.$eval(onExp);
							if (thisVal === compareWithVal) {
								newActivated = instance;
								return true;
							}
						});
						if (data.lastActivated !== newActivated) {
							if (data.lastActivated) {
								$animate.removeClass(data.lastActivated.element, clazz);
							}
							if (newActivated) {
								$animate.addClass(newActivated.element, clazz);
							}
							data.lastActivated = newActivated;
						}
					};
					expToData[exp] = data = {
						lastActivated: null,
						scope: scope,
						watchFn: watchFn,
						compareWithExp: compareWithExp,
						watcher: scope.$watch(compareWithExp, watchFn)
					};
				}
				data.watchFn(scope.$eval(compareWithExp));
			}

			function removeScope(e) {
				var removedScope = e.targetScope;
				var index = linkedScopes.indexOf(removedScope);
				linkedScopes.splice(index, 1);
				instances.splice(index, 1);
				if (linkedScopes.length) {
					var newWatchScope = linkedScopes[0];
					angular.forEach(expToData, function (data) {
						if (data.scope === removedScope) {
							data.watcher = newWatchScope.$watch(data.compareWithExp, data.watchFn);
							data.scope = newWatchScope;
						}
					});
				} else {
					expToData = {};
				}
			}
		}
	};
}]);
angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.dateparser', 'ui.bootstrap.isClass'])

.value('$datepickerSuppressError', false)

.value('$datepickerLiteralWarning', true)

.constant('uibDatepickerConfig', {
	datepickerMode: 'day',
	formatDay: 'dd',
	formatMonth: 'MMMM',
	formatYear: 'yyyy',
	formatDayHeader: 'EEE',
	formatDayTitle: 'MMMM yyyy',
	formatMonthTitle: 'yyyy',
	maxDate: null,
	maxMode: 'year',
	minDate: null,
	minMode: 'day',
	monthColumns: 3,
	ngModelOptions: {},
	shortcutPropagation: false,
	showWeeks: true,
	yearColumns: 5,
	yearRows: 4
})

.controller('UibDatepickerController', ['$scope', '$element', '$attrs', '$parse', '$interpolate', '$locale', '$log', 'dateFilter', 'uibDatepickerConfig', '$datepickerLiteralWarning', '$datepickerSuppressError', 'uibDateParser',
  function ($scope, $element, $attrs, $parse, $interpolate, $locale, $log, dateFilter, datepickerConfig, $datepickerLiteralWarning, $datepickerSuppressError, dateParser) {
  	var self = this,
		ngModelCtrl = { $setViewValue: angular.noop }, // nullModelCtrl;
		ngModelOptions = {},
		watchListeners = [];

  	$element.addClass('uib-datepicker');
  	$attrs.$set('role', 'application');

  	if (!$scope.datepickerOptions) {
  		$scope.datepickerOptions = {};
  	}

  	// Modes chain
  	this.modes = ['day', 'month', 'year'];

  	[
	  'customClass',
	  'dateDisabled',
	  'datepickerMode',
	  'formatDay',
	  'formatDayHeader',
	  'formatDayTitle',
	  'formatMonth',
	  'formatMonthTitle',
	  'formatYear',
	  'maxDate',
	  'maxMode',
	  'minDate',
	  'minMode',
	  'monthColumns',
	  'showWeeks',
	  'shortcutPropagation',
	  'startingDay',
	  'yearColumns',
	  'yearRows'
  	].forEach(function (key) {
  		switch (key) {
  			case 'customClass':
  			case 'dateDisabled':
  				$scope[key] = $scope.datepickerOptions[key] || angular.noop;
  				break;
  			case 'datepickerMode':
  				$scope.datepickerMode = angular.isDefined($scope.datepickerOptions.datepickerMode) ?
				  $scope.datepickerOptions.datepickerMode : datepickerConfig.datepickerMode;
  				break;
  			case 'formatDay':
  			case 'formatDayHeader':
  			case 'formatDayTitle':
  			case 'formatMonth':
  			case 'formatMonthTitle':
  			case 'formatYear':
  				self[key] = angular.isDefined($scope.datepickerOptions[key]) ?
				  $interpolate($scope.datepickerOptions[key])($scope.$parent) :
				  datepickerConfig[key];
  				break;
  			case 'monthColumns':
  			case 'showWeeks':
  			case 'shortcutPropagation':
  			case 'yearColumns':
  			case 'yearRows':
  				self[key] = angular.isDefined($scope.datepickerOptions[key]) ?
				  $scope.datepickerOptions[key] : datepickerConfig[key];
  				break;
  			case 'startingDay':
  				if (angular.isDefined($scope.datepickerOptions.startingDay)) {
  					self.startingDay = $scope.datepickerOptions.startingDay;
  				} else if (angular.isNumber(datepickerConfig.startingDay)) {
  					self.startingDay = datepickerConfig.startingDay;
  				} else {
  					self.startingDay = ($locale.DATETIME_FORMATS.FIRSTDAYOFWEEK + 8) % 7;
  				}

  				break;
  			case 'maxDate':
  			case 'minDate':
  				$scope.$watch('datepickerOptions.' + key, function (value) {
  					if (value) {
  						if (angular.isDate(value)) {
  							self[key] = dateParser.fromTimezone(new Date(value), ngModelOptions.timezone);
  						} else {
  							if ($datepickerLiteralWarning) {
  								$log.warn('Literal date support has been deprecated, please switch to date object usage');
  							}

  							self[key] = new Date(dateFilter(value, 'medium'));
  						}
  					} else {
  						self[key] = datepickerConfig[key] ?
						  dateParser.fromTimezone(new Date(datepickerConfig[key]), ngModelOptions.timezone) :
						  null;
  					}

  					self.refreshView();
  				});

  				break;
  			case 'maxMode':
  			case 'minMode':
  				if ($scope.datepickerOptions[key]) {
  					$scope.$watch(function () { return $scope.datepickerOptions[key]; }, function (value) {
  						self[key] = $scope[key] = angular.isDefined(value) ? value : $scope.datepickerOptions[key];
  						if (key === 'minMode' && self.modes.indexOf($scope.datepickerOptions.datepickerMode) < self.modes.indexOf(self[key]) ||
						  key === 'maxMode' && self.modes.indexOf($scope.datepickerOptions.datepickerMode) > self.modes.indexOf(self[key])) {
  							$scope.datepickerMode = self[key];
  							$scope.datepickerOptions.datepickerMode = self[key];
  						}
  					});
  				} else {
  					self[key] = $scope[key] = datepickerConfig[key] || null;
  				}

  				break;
  		}
  	});

  	$scope.uniqueId = 'datepicker-' + $scope.$id + '-' + Math.floor(Math.random() * 10000);

  	$scope.disabled = angular.isDefined($attrs.disabled) || false;
  	if (angular.isDefined($attrs.ngDisabled)) {
  		watchListeners.push($scope.$parent.$watch($attrs.ngDisabled, function (disabled) {
  			$scope.disabled = disabled;
  			self.refreshView();
  		}));
  	}

  	$scope.isActive = function (dateObject) {
  		if (self.compare(dateObject.date, self.activeDate) === 0) {
  			$scope.activeDateId = dateObject.uid;
  			return true;
  		}
  		return false;
  	};

  	this.init = function (ngModelCtrl_) {
  		ngModelCtrl = ngModelCtrl_;
  		ngModelOptions = ngModelCtrl_.$options ||
		  $scope.datepickerOptions.ngModelOptions ||
		  datepickerConfig.ngModelOptions;
  		if ($scope.datepickerOptions.initDate) {
  			self.activeDate = dateParser.fromTimezone($scope.datepickerOptions.initDate, ngModelOptions.timezone) || new Date();
  			$scope.$watch('datepickerOptions.initDate', function (initDate) {
  				if (initDate && (ngModelCtrl.$isEmpty(ngModelCtrl.$modelValue) || ngModelCtrl.$invalid)) {
  					self.activeDate = dateParser.fromTimezone(initDate, ngModelOptions.timezone);
  					self.refreshView();
  				}
  			});
  		} else {
  			self.activeDate = new Date();
  		}

  		var date = ngModelCtrl.$modelValue ? new Date(ngModelCtrl.$modelValue) : new Date();
  		this.activeDate = !isNaN(date) ?
		  dateParser.fromTimezone(date, ngModelOptions.timezone) :
		  dateParser.fromTimezone(new Date(), ngModelOptions.timezone);

  		ngModelCtrl.$render = function () {
  			self.render();
  		};
  	};

  	this.render = function () {
  		if (ngModelCtrl.$viewValue) {
  			var date = new Date(ngModelCtrl.$viewValue),
				isValid = !isNaN(date);

  			if (isValid) {
  				this.activeDate = dateParser.fromTimezone(date, ngModelOptions.timezone);
  			} else if (!$datepickerSuppressError) {
  				$log.error('Datepicker directive: "ng-model" value must be a Date object');
  			}
  		}
  		this.refreshView();
  	};

  	this.refreshView = function () {
  		if (this.element) {
  			$scope.selectedDt = null;
  			this._refreshView();
  			if ($scope.activeDt) {
  				$scope.activeDateId = $scope.activeDt.uid;
  			}

  			var date = ngModelCtrl.$viewValue ? new Date(ngModelCtrl.$viewValue) : null;
  			date = dateParser.fromTimezone(date, ngModelOptions.timezone);
  			ngModelCtrl.$setValidity('dateDisabled', !date ||
			  this.element && !this.isDisabled(date));
  		}
  	};

  	this.createDateObject = function (date, format) {
  		var model = ngModelCtrl.$viewValue ? new Date(ngModelCtrl.$viewValue) : null;
  		model = dateParser.fromTimezone(model, ngModelOptions.timezone);
  		var today = new Date();
  		today = dateParser.fromTimezone(today, ngModelOptions.timezone);
  		var time = this.compare(date, today);
  		var dt = {
  			date: date,
  			label: dateParser.filter(date, format),
  			selected: model && this.compare(date, model) === 0,
  			disabled: this.isDisabled(date),
  			past: time < 0,
  			current: time === 0,
  			future: time > 0,
  			customClass: this.customClass(date) || null
  		};

  		if (model && this.compare(date, model) === 0) {
  			$scope.selectedDt = dt;
  		}

  		if (self.activeDate && this.compare(dt.date, self.activeDate) === 0) {
  			$scope.activeDt = dt;
  		}

  		return dt;
  	};

  	this.isDisabled = function (date) {
  		return $scope.disabled ||
		  this.minDate && this.compare(date, this.minDate) < 0 ||
		  this.maxDate && this.compare(date, this.maxDate) > 0 ||
		  $scope.dateDisabled && $scope.dateDisabled({ date: date, mode: $scope.datepickerMode });
  	};

  	this.customClass = function (date) {
  		return $scope.customClass({ date: date, mode: $scope.datepickerMode });
  	};

  	// Split array into smaller arrays
  	this.split = function (arr, size) {
  		var arrays = [];
  		while (arr.length > 0) {
  			arrays.push(arr.splice(0, size));
  		}
  		return arrays;
  	};

  	$scope.select = function (date) {
  		if ($scope.datepickerMode === self.minMode) {
  			var dt = ngModelCtrl.$viewValue ? dateParser.fromTimezone(new Date(ngModelCtrl.$viewValue), ngModelOptions.timezone) : new Date(0, 0, 0, 0, 0, 0, 0);
  			dt.setFullYear(date.getFullYear(), date.getMonth(), date.getDate());
  			dt = dateParser.toTimezone(dt, ngModelOptions.timezone);
  			ngModelCtrl.$setViewValue(dt);
  			ngModelCtrl.$render();
  		} else {
  			self.activeDate = date;
  			setMode(self.modes[self.modes.indexOf($scope.datepickerMode) - 1]);

  			$scope.$emit('uib:datepicker.mode');
  		}

  		$scope.$broadcast('uib:datepicker.focus');
  	};

  	$scope.move = function (direction) {
  		var year = self.activeDate.getFullYear() + direction * (self.step.years || 0),
			month = self.activeDate.getMonth() + direction * (self.step.months || 0);
  		self.activeDate.setFullYear(year, month, 1);
  		self.refreshView();
  	};

  	$scope.toggleMode = function (direction) {
  		direction = direction || 1;

  		if ($scope.datepickerMode === self.maxMode && direction === 1 ||
		  $scope.datepickerMode === self.minMode && direction === -1) {
  			return;
  		}

  		setMode(self.modes[self.modes.indexOf($scope.datepickerMode) + direction]);

  		$scope.$emit('uib:datepicker.mode');
  	};

  	// Key event mapper
  	$scope.keys = { 13: 'enter', 32: 'space', 33: 'pageup', 34: 'pagedown', 35: 'end', 36: 'home', 37: 'left', 38: 'up', 39: 'right', 40: 'down' };

  	var focusElement = function () {
  		self.element[0].focus();
  	};

  	// Listen for focus requests from popup directive
  	$scope.$on('uib:datepicker.focus', focusElement);

  	$scope.keydown = function (evt) {
  		var key = $scope.keys[evt.which];

  		if (!key || evt.shiftKey || evt.altKey || $scope.disabled) {
  			return;
  		}

  		evt.preventDefault();
  		if (!self.shortcutPropagation) {
  			evt.stopPropagation();
  		}

  		if (key === 'enter' || key === 'space') {
  			if (self.isDisabled(self.activeDate)) {
  				return; // do nothing
  			}
  			$scope.select(self.activeDate);
  		} else if (evt.ctrlKey && (key === 'up' || key === 'down')) {
  			$scope.toggleMode(key === 'up' ? 1 : -1);
  		} else {
  			self.handleKeyDown(key, evt);
  			self.refreshView();
  		}
  	};

  	$element.on('keydown', function (evt) {
  		$scope.$apply(function () {
  			$scope.keydown(evt);
  		});
  	});

  	$scope.$on('$destroy', function () {
  		//Clear all watch listeners on destroy
  		while (watchListeners.length) {
  			watchListeners.shift()();
  		}
  	});

  	function setMode(mode) {
  		$scope.datepickerMode = mode;
  		$scope.datepickerOptions.datepickerMode = mode;
  	}
  }])

.controller('UibDaypickerController', ['$scope', '$element', 'dateFilter', function (scope, $element, dateFilter) {
	var DAYS_IN_MONTH = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];

	this.step = { months: 1 };
	this.element = $element;
	function getDaysInMonth(year, month) {
		return month === 1 && year % 4 === 0 &&
		  (year % 100 !== 0 || year % 400 === 0) ? 29 : DAYS_IN_MONTH[month];
	}

	this.init = function (ctrl) {
		angular.extend(ctrl, this);
		scope.showWeeks = ctrl.showWeeks;
		ctrl.refreshView();
	};

	this.getDates = function (startDate, n) {
		var dates = new Array(n), current = new Date(startDate), i = 0, date;
		while (i < n) {
			date = new Date(current);
			dates[i++] = date;
			current.setDate(current.getDate() + 1);
		}
		return dates;
	};

	this._refreshView = function () {
		var year = this.activeDate.getFullYear(),
		  month = this.activeDate.getMonth(),
		  firstDayOfMonth = new Date(this.activeDate);

		firstDayOfMonth.setFullYear(year, month, 1);

		var difference = this.startingDay - firstDayOfMonth.getDay(),
		  numDisplayedFromPreviousMonth = difference > 0 ?
			7 - difference : -difference,
		  firstDate = new Date(firstDayOfMonth);

		if (numDisplayedFromPreviousMonth > 0) {
			firstDate.setDate(-numDisplayedFromPreviousMonth + 1);
		}

		// 42 is the number of days on a six-week calendar
		var days = this.getDates(firstDate, 42);
		for (var i = 0; i < 42; i++) {
			days[i] = angular.extend(this.createDateObject(days[i], this.formatDay), {
				secondary: days[i].getMonth() !== month,
				uid: scope.uniqueId + '-' + i
			});
		}

		scope.labels = new Array(7);
		for (var j = 0; j < 7; j++) {
			scope.labels[j] = {
				abbr: dateFilter(days[j].date, this.formatDayHeader),
				full: dateFilter(days[j].date, 'EEEE')
			};
		}

		scope.title = dateFilter(this.activeDate, this.formatDayTitle);
		scope.rows = this.split(days, 7);

		if (scope.showWeeks) {
			scope.weekNumbers = [];
			var thursdayIndex = (4 + 7 - this.startingDay) % 7,
				numWeeks = scope.rows.length;
			for (var curWeek = 0; curWeek < numWeeks; curWeek++) {
				scope.weekNumbers.push(
				  getISO8601WeekNumber(scope.rows[curWeek][thursdayIndex].date));
			}
		}
	};

	this.compare = function (date1, date2) {
		var _date1 = new Date(date1.getFullYear(), date1.getMonth(), date1.getDate());
		var _date2 = new Date(date2.getFullYear(), date2.getMonth(), date2.getDate());
		_date1.setFullYear(date1.getFullYear());
		_date2.setFullYear(date2.getFullYear());
		return _date1 - _date2;
	};

	function getISO8601WeekNumber(date) {
		var checkDate = new Date(date);
		checkDate.setDate(checkDate.getDate() + 4 - (checkDate.getDay() || 7)); // Thursday
		var time = checkDate.getTime();
		checkDate.setMonth(0); // Compare with Jan 1
		checkDate.setDate(1);
		return Math.floor(Math.round((time - checkDate) / 86400000) / 7) + 1;
	}

	this.handleKeyDown = function (key, evt) {
		var date = this.activeDate.getDate();

		if (key === 'left') {
			date = date - 1;
		} else if (key === 'up') {
			date = date - 7;
		} else if (key === 'right') {
			date = date + 1;
		} else if (key === 'down') {
			date = date + 7;
		} else if (key === 'pageup' || key === 'pagedown') {
			var month = this.activeDate.getMonth() + (key === 'pageup' ? -1 : 1);
			this.activeDate.setMonth(month, 1);
			date = Math.min(getDaysInMonth(this.activeDate.getFullYear(), this.activeDate.getMonth()), date);
		} else if (key === 'home') {
			date = 1;
		} else if (key === 'end') {
			date = getDaysInMonth(this.activeDate.getFullYear(), this.activeDate.getMonth());
		}
		this.activeDate.setDate(date);
	};
}])

.controller('UibMonthpickerController', ['$scope', '$element', 'dateFilter', function (scope, $element, dateFilter) {
	this.step = { years: 1 };
	this.element = $element;

	this.init = function (ctrl) {
		angular.extend(ctrl, this);
		ctrl.refreshView();
	};

	this._refreshView = function () {
		var months = new Array(12),
			year = this.activeDate.getFullYear(),
			date;

		for (var i = 0; i < 12; i++) {
			date = new Date(this.activeDate);
			date.setFullYear(year, i, 1);
			months[i] = angular.extend(this.createDateObject(date, this.formatMonth), {
				uid: scope.uniqueId + '-' + i
			});
		}

		scope.title = dateFilter(this.activeDate, this.formatMonthTitle);
		scope.rows = this.split(months, this.monthColumns);
		scope.yearHeaderColspan = this.monthColumns > 3 ? this.monthColumns - 2 : 1;
	};

	this.compare = function (date1, date2) {
		var _date1 = new Date(date1.getFullYear(), date1.getMonth());
		var _date2 = new Date(date2.getFullYear(), date2.getMonth());
		_date1.setFullYear(date1.getFullYear());
		_date2.setFullYear(date2.getFullYear());
		return _date1 - _date2;
	};

	this.handleKeyDown = function (key, evt) {
		var date = this.activeDate.getMonth();

		if (key === 'left') {
			date = date - 1;
		} else if (key === 'up') {
			date = date - this.monthColumns;
		} else if (key === 'right') {
			date = date + 1;
		} else if (key === 'down') {
			date = date + this.monthColumns;
		} else if (key === 'pageup' || key === 'pagedown') {
			var year = this.activeDate.getFullYear() + (key === 'pageup' ? -1 : 1);
			this.activeDate.setFullYear(year);
		} else if (key === 'home') {
			date = 0;
		} else if (key === 'end') {
			date = 11;
		}
		this.activeDate.setMonth(date);
	};
}])

.controller('UibYearpickerController', ['$scope', '$element', 'dateFilter', function (scope, $element, dateFilter) {
	var columns, range;
	this.element = $element;

	function getStartingYear(year) {
		return parseInt((year - 1) / range, 10) * range + 1;
	}

	this.yearpickerInit = function () {
		columns = this.yearColumns;
		range = this.yearRows * columns;
		this.step = { years: range };
	};

	this._refreshView = function () {
		var years = new Array(range), date;

		for (var i = 0, start = getStartingYear(this.activeDate.getFullYear()) ; i < range; i++) {
			date = new Date(this.activeDate);
			date.setFullYear(start + i, 0, 1);
			years[i] = angular.extend(this.createDateObject(date, this.formatYear), {
				uid: scope.uniqueId + '-' + i
			});
		}

		scope.title = [years[0].label, years[range - 1].label].join(' - ');
		scope.rows = this.split(years, columns);
		scope.columns = columns;
	};

	this.compare = function (date1, date2) {
		return date1.getFullYear() - date2.getFullYear();
	};

	this.handleKeyDown = function (key, evt) {
		var date = this.activeDate.getFullYear();

		if (key === 'left') {
			date = date - 1;
		} else if (key === 'up') {
			date = date - columns;
		} else if (key === 'right') {
			date = date + 1;
		} else if (key === 'down') {
			date = date + columns;
		} else if (key === 'pageup' || key === 'pagedown') {
			date += (key === 'pageup' ? -1 : 1) * range;
		} else if (key === 'home') {
			date = getStartingYear(this.activeDate.getFullYear());
		} else if (key === 'end') {
			date = getStartingYear(this.activeDate.getFullYear()) + range - 1;
		}
		this.activeDate.setFullYear(date);
	};
}])

.directive('uibDatepicker', function () {
	return {
		templateUrl: function (element, attrs) {
			return attrs.templateUrl || 'uib/template/datepicker/datepicker.html';
		},
		scope: {
			datepickerOptions: '=?'
		},
		require: ['uibDatepicker', '^ngModel'],
		restrict: 'A',
		controller: 'UibDatepickerController',
		controllerAs: 'datepicker',
		link: function (scope, element, attrs, ctrls) {
			var datepickerCtrl = ctrls[0], ngModelCtrl = ctrls[1];

			datepickerCtrl.init(ngModelCtrl);
		}
	};
})

.directive('uibDaypicker', function () {
	return {
		templateUrl: function (element, attrs) {
			return attrs.templateUrl || 'uib/template/datepicker/day.html';
		},
		require: ['^uibDatepicker', 'uibDaypicker'],
		restrict: 'A',
		controller: 'UibDaypickerController',
		link: function (scope, element, attrs, ctrls) {
			var datepickerCtrl = ctrls[0],
			  daypickerCtrl = ctrls[1];

			daypickerCtrl.init(datepickerCtrl);
		}
	};
})

.directive('uibMonthpicker', function () {
	return {
		templateUrl: function (element, attrs) {
			return attrs.templateUrl || 'uib/template/datepicker/month.html';
		},
		require: ['^uibDatepicker', 'uibMonthpicker'],
		restrict: 'A',
		controller: 'UibMonthpickerController',
		link: function (scope, element, attrs, ctrls) {
			var datepickerCtrl = ctrls[0],
			  monthpickerCtrl = ctrls[1];

			monthpickerCtrl.init(datepickerCtrl);
		}
	};
})

.directive('uibYearpicker', function () {
	return {
		templateUrl: function (element, attrs) {
			return attrs.templateUrl || 'uib/template/datepicker/year.html';
		},
		require: ['^uibDatepicker', 'uibYearpicker'],
		restrict: 'A',
		controller: 'UibYearpickerController',
		link: function (scope, element, attrs, ctrls) {
			var ctrl = ctrls[0];
			angular.extend(ctrl, ctrls[1]);
			ctrl.yearpickerInit();

			ctrl.refreshView();
		}
	};
});

angular.module('ui.bootstrap.position', [])

/**
 * A set of utility methods for working with the DOM.
 * It is meant to be used where we need to absolute-position elements in
 * relation to another element (this is the case for tooltips, popovers,
 * typeahead suggestions etc.).
 */
  .factory('$uibPosition', ['$document', '$window', function ($document, $window) {
  	/**
     * Used by scrollbarWidth() function to cache scrollbar's width.
     * Do not access this variable directly, use scrollbarWidth() instead.
     */
  	var SCROLLBAR_WIDTH;
  	/**
     * scrollbar on body and html element in IE and Edge overlay
     * content and should be considered 0 width.
     */
  	var BODY_SCROLLBAR_WIDTH;
  	var OVERFLOW_REGEX = {
  		normal: /(auto|scroll)/,
  		hidden: /(auto|scroll|hidden)/
  	};
  	var PLACEMENT_REGEX = {
  		auto: /\s?auto?\s?/i,
  		primary: /^(top|bottom|left|right)$/,
  		secondary: /^(top|bottom|left|right|center)$/,
  		vertical: /^(top|bottom)$/
  	};
  	var BODY_REGEX = /(HTML|BODY)/;

  	return {

  		/**
		 * Provides a raw DOM element from a jQuery/jQLite element.
		 *
		 * @param {element} elem - The element to convert.
		 *
		 * @returns {element} A HTML element.
		 */
  		getRawNode: function (elem) {
  			return elem.nodeName ? elem : elem[0] || elem;
  		},

  		/**
		 * Provides a parsed number for a style property.  Strips
		 * units and casts invalid numbers to 0.
		 *
		 * @param {string} value - The style value to parse.
		 *
		 * @returns {number} A valid number.
		 */
  		parseStyle: function (value) {
  			value = parseFloat(value);
  			return isFinite(value) ? value : 0;
  		},

  		/**
		 * Provides the closest positioned ancestor.
		 *
		 * @param {element} element - The element to get the offest parent for.
		 *
		 * @returns {element} The closest positioned ancestor.
		 */
  		offsetParent: function (elem) {
  			elem = this.getRawNode(elem);

  			var offsetParent = elem.offsetParent || $document[0].documentElement;

  			function isStaticPositioned(el) {
  				return ($window.getComputedStyle(el).position || 'static') === 'static';
  			}

  			while (offsetParent && offsetParent !== $document[0].documentElement && isStaticPositioned(offsetParent)) {
  				offsetParent = offsetParent.offsetParent;
  			}

  			return offsetParent || $document[0].documentElement;
  		},

  		/**
		 * Provides the scrollbar width, concept from TWBS measureScrollbar()
		 * function in https://github.com/twbs/bootstrap/blob/master/js/modal.js
		 * In IE and Edge, scollbar on body and html element overlay and should
		 * return a width of 0.
		 *
		 * @returns {number} The width of the browser scollbar.
		 */
  		scrollbarWidth: function (isBody) {
  			if (isBody) {
  				if (angular.isUndefined(BODY_SCROLLBAR_WIDTH)) {
  					var bodyElem = $document.find('body');
  					bodyElem.addClass('uib-position-body-scrollbar-measure');
  					BODY_SCROLLBAR_WIDTH = $window.innerWidth - bodyElem[0].clientWidth;
  					BODY_SCROLLBAR_WIDTH = isFinite(BODY_SCROLLBAR_WIDTH) ? BODY_SCROLLBAR_WIDTH : 0;
  					bodyElem.removeClass('uib-position-body-scrollbar-measure');
  				}
  				return BODY_SCROLLBAR_WIDTH;
  			}

  			if (angular.isUndefined(SCROLLBAR_WIDTH)) {
  				var scrollElem = angular.element('<div class="uib-position-scrollbar-measure"></div>');
  				$document.find('body').append(scrollElem);
  				SCROLLBAR_WIDTH = scrollElem[0].offsetWidth - scrollElem[0].clientWidth;
  				SCROLLBAR_WIDTH = isFinite(SCROLLBAR_WIDTH) ? SCROLLBAR_WIDTH : 0;
  				scrollElem.remove();
  			}

  			return SCROLLBAR_WIDTH;
  		},

  		/**
		 * Provides the padding required on an element to replace the scrollbar.
		 *
		 * @returns {object} An object with the following properties:
		 *   <ul>
		 *     <li>**scrollbarWidth**: the width of the scrollbar</li>
		 *     <li>**widthOverflow**: whether the the width is overflowing</li>
		 *     <li>**right**: the amount of right padding on the element needed to replace the scrollbar</li>
		 *     <li>**rightOriginal**: the amount of right padding currently on the element</li>
		 *     <li>**heightOverflow**: whether the the height is overflowing</li>
		 *     <li>**bottom**: the amount of bottom padding on the element needed to replace the scrollbar</li>
		 *     <li>**bottomOriginal**: the amount of bottom padding currently on the element</li>
		 *   </ul>
		 */
  		scrollbarPadding: function (elem) {
  			elem = this.getRawNode(elem);

  			var elemStyle = $window.getComputedStyle(elem);
  			var paddingRight = this.parseStyle(elemStyle.paddingRight);
  			var paddingBottom = this.parseStyle(elemStyle.paddingBottom);
  			var scrollParent = this.scrollParent(elem, false, true);
  			var scrollbarWidth = this.scrollbarWidth(scrollParent, BODY_REGEX.test(scrollParent.tagName));

  			return {
  				scrollbarWidth: scrollbarWidth,
  				widthOverflow: scrollParent.scrollWidth > scrollParent.clientWidth,
  				right: paddingRight + scrollbarWidth,
  				originalRight: paddingRight,
  				heightOverflow: scrollParent.scrollHeight > scrollParent.clientHeight,
  				bottom: paddingBottom + scrollbarWidth,
  				originalBottom: paddingBottom
  			};
  		},

  		/**
		 * Checks to see if the element is scrollable.
		 *
		 * @param {element} elem - The element to check.
		 * @param {boolean=} [includeHidden=false] - Should scroll style of 'hidden' be considered,
		 *   default is false.
		 *
		 * @returns {boolean} Whether the element is scrollable.
		 */
  		isScrollable: function (elem, includeHidden) {
  			elem = this.getRawNode(elem);

  			var overflowRegex = includeHidden ? OVERFLOW_REGEX.hidden : OVERFLOW_REGEX.normal;
  			var elemStyle = $window.getComputedStyle(elem);
  			return overflowRegex.test(elemStyle.overflow + elemStyle.overflowY + elemStyle.overflowX);
  		},

  		/**
		 * Provides the closest scrollable ancestor.
		 * A port of the jQuery UI scrollParent method:
		 * https://github.com/jquery/jquery-ui/blob/master/ui/scroll-parent.js
		 *
		 * @param {element} elem - The element to find the scroll parent of.
		 * @param {boolean=} [includeHidden=false] - Should scroll style of 'hidden' be considered,
		 *   default is false.
		 * @param {boolean=} [includeSelf=false] - Should the element being passed be
		 * included in the scrollable llokup.
		 *
		 * @returns {element} A HTML element.
		 */
  		scrollParent: function (elem, includeHidden, includeSelf) {
  			elem = this.getRawNode(elem);

  			var overflowRegex = includeHidden ? OVERFLOW_REGEX.hidden : OVERFLOW_REGEX.normal;
  			var documentEl = $document[0].documentElement;
  			var elemStyle = $window.getComputedStyle(elem);
  			if (includeSelf && overflowRegex.test(elemStyle.overflow + elemStyle.overflowY + elemStyle.overflowX)) {
  				return elem;
  			}
  			var excludeStatic = elemStyle.position === 'absolute';
  			var scrollParent = elem.parentElement || documentEl;

  			if (scrollParent === documentEl || elemStyle.position === 'fixed') {
  				return documentEl;
  			}

  			while (scrollParent.parentElement && scrollParent !== documentEl) {
  				var spStyle = $window.getComputedStyle(scrollParent);
  				if (excludeStatic && spStyle.position !== 'static') {
  					excludeStatic = false;
  				}

  				if (!excludeStatic && overflowRegex.test(spStyle.overflow + spStyle.overflowY + spStyle.overflowX)) {
  					break;
  				}
  				scrollParent = scrollParent.parentElement;
  			}

  			return scrollParent;
  		},

  		/**
		 * Provides read-only equivalent of jQuery's position function:
		 * http://api.jquery.com/position/ - distance to closest positioned
		 * ancestor.  Does not account for margins by default like jQuery position.
		 *
		 * @param {element} elem - The element to caclulate the position on.
		 * @param {boolean=} [includeMargins=false] - Should margins be accounted
		 * for, default is false.
		 *
		 * @returns {object} An object with the following properties:
		 *   <ul>
		 *     <li>**width**: the width of the element</li>
		 *     <li>**height**: the height of the element</li>
		 *     <li>**top**: distance to top edge of offset parent</li>
		 *     <li>**left**: distance to left edge of offset parent</li>
		 *   </ul>
		 */
  		position: function (elem, includeMagins) {
  			elem = this.getRawNode(elem);

  			var elemOffset = this.offset(elem);
  			if (includeMagins) {
  				var elemStyle = $window.getComputedStyle(elem);
  				elemOffset.top -= this.parseStyle(elemStyle.marginTop);
  				elemOffset.left -= this.parseStyle(elemStyle.marginLeft);
  			}
  			var parent = this.offsetParent(elem);
  			var parentOffset = { top: 0, left: 0 };

  			if (parent !== $document[0].documentElement) {
  				parentOffset = this.offset(parent);
  				parentOffset.top += parent.clientTop - parent.scrollTop;
  				parentOffset.left += parent.clientLeft - parent.scrollLeft;
  			}

  			return {
  				width: Math.round(angular.isNumber(elemOffset.width) ? elemOffset.width : elem.offsetWidth),
  				height: Math.round(angular.isNumber(elemOffset.height) ? elemOffset.height : elem.offsetHeight),
  				top: Math.round(elemOffset.top - parentOffset.top),
  				left: Math.round(elemOffset.left - parentOffset.left)
  			};
  		},

  		/**
		 * Provides read-only equivalent of jQuery's offset function:
		 * http://api.jquery.com/offset/ - distance to viewport.  Does
		 * not account for borders, margins, or padding on the body
		 * element.
		 *
		 * @param {element} elem - The element to calculate the offset on.
		 *
		 * @returns {object} An object with the following properties:
		 *   <ul>
		 *     <li>**width**: the width of the element</li>
		 *     <li>**height**: the height of the element</li>
		 *     <li>**top**: distance to top edge of viewport</li>
		 *     <li>**right**: distance to bottom edge of viewport</li>
		 *   </ul>
		 */
  		offset: function (elem) {
  			elem = this.getRawNode(elem);

  			var elemBCR = elem.getBoundingClientRect();
  			return {
  				width: Math.round(angular.isNumber(elemBCR.width) ? elemBCR.width : elem.offsetWidth),
  				height: Math.round(angular.isNumber(elemBCR.height) ? elemBCR.height : elem.offsetHeight),
  				top: Math.round(elemBCR.top + ($window.pageYOffset || $document[0].documentElement.scrollTop)),
  				left: Math.round(elemBCR.left + ($window.pageXOffset || $document[0].documentElement.scrollLeft))
  			};
  		},

  		/**
		 * Provides offset distance to the closest scrollable ancestor
		 * or viewport.  Accounts for border and scrollbar width.
		 *
		 * Right and bottom dimensions represent the distance to the
		 * respective edge of the viewport element.  If the element
		 * edge extends beyond the viewport, a negative value will be
		 * reported.
		 *
		 * @param {element} elem - The element to get the viewport offset for.
		 * @param {boolean=} [useDocument=false] - Should the viewport be the document element instead
		 * of the first scrollable element, default is false.
		 * @param {boolean=} [includePadding=true] - Should the padding on the offset parent element
		 * be accounted for, default is true.
		 *
		 * @returns {object} An object with the following properties:
		 *   <ul>
		 *     <li>**top**: distance to the top content edge of viewport element</li>
		 *     <li>**bottom**: distance to the bottom content edge of viewport element</li>
		 *     <li>**left**: distance to the left content edge of viewport element</li>
		 *     <li>**right**: distance to the right content edge of viewport element</li>
		 *   </ul>
		 */
  		viewportOffset: function (elem, useDocument, includePadding) {
  			elem = this.getRawNode(elem);
  			includePadding = includePadding !== false ? true : false;

  			var elemBCR = elem.getBoundingClientRect();
  			var offsetBCR = { top: 0, left: 0, bottom: 0, right: 0 };

  			var offsetParent = useDocument ? $document[0].documentElement : this.scrollParent(elem);
  			var offsetParentBCR = offsetParent.getBoundingClientRect();

  			offsetBCR.top = offsetParentBCR.top + offsetParent.clientTop;
  			offsetBCR.left = offsetParentBCR.left + offsetParent.clientLeft;
  			if (offsetParent === $document[0].documentElement) {
  				offsetBCR.top += $window.pageYOffset;
  				offsetBCR.left += $window.pageXOffset;
  			}
  			offsetBCR.bottom = offsetBCR.top + offsetParent.clientHeight;
  			offsetBCR.right = offsetBCR.left + offsetParent.clientWidth;

  			if (includePadding) {
  				var offsetParentStyle = $window.getComputedStyle(offsetParent);
  				offsetBCR.top += this.parseStyle(offsetParentStyle.paddingTop);
  				offsetBCR.bottom -= this.parseStyle(offsetParentStyle.paddingBottom);
  				offsetBCR.left += this.parseStyle(offsetParentStyle.paddingLeft);
  				offsetBCR.right -= this.parseStyle(offsetParentStyle.paddingRight);
  			}

  			return {
  				top: Math.round(elemBCR.top - offsetBCR.top),
  				bottom: Math.round(offsetBCR.bottom - elemBCR.bottom),
  				left: Math.round(elemBCR.left - offsetBCR.left),
  				right: Math.round(offsetBCR.right - elemBCR.right)
  			};
  		},

  		/**
		 * Provides an array of placement values parsed from a placement string.
		 * Along with the 'auto' indicator, supported placement strings are:
		 *   <ul>
		 *     <li>top: element on top, horizontally centered on host element.</li>
		 *     <li>top-left: element on top, left edge aligned with host element left edge.</li>
		 *     <li>top-right: element on top, lerightft edge aligned with host element right edge.</li>
		 *     <li>bottom: element on bottom, horizontally centered on host element.</li>
		 *     <li>bottom-left: element on bottom, left edge aligned with host element left edge.</li>
		 *     <li>bottom-right: element on bottom, right edge aligned with host element right edge.</li>
		 *     <li>left: element on left, vertically centered on host element.</li>
		 *     <li>left-top: element on left, top edge aligned with host element top edge.</li>
		 *     <li>left-bottom: element on left, bottom edge aligned with host element bottom edge.</li>
		 *     <li>right: element on right, vertically centered on host element.</li>
		 *     <li>right-top: element on right, top edge aligned with host element top edge.</li>
		 *     <li>right-bottom: element on right, bottom edge aligned with host element bottom edge.</li>
		 *   </ul>
		 * A placement string with an 'auto' indicator is expected to be
		 * space separated from the placement, i.e: 'auto bottom-left'  If
		 * the primary and secondary placement values do not match 'top,
		 * bottom, left, right' then 'top' will be the primary placement and
		 * 'center' will be the secondary placement.  If 'auto' is passed, true
		 * will be returned as the 3rd value of the array.
		 *
		 * @param {string} placement - The placement string to parse.
		 *
		 * @returns {array} An array with the following values
		 * <ul>
		 *   <li>**[0]**: The primary placement.</li>
		 *   <li>**[1]**: The secondary placement.</li>
		 *   <li>**[2]**: If auto is passed: true, else undefined.</li>
		 * </ul>
		 */
  		parsePlacement: function (placement) {
  			var autoPlace = PLACEMENT_REGEX.auto.test(placement);
  			if (autoPlace) {
  				placement = placement.replace(PLACEMENT_REGEX.auto, '');
  			}

  			placement = placement.split('-');

  			placement[0] = placement[0] || 'top';
  			if (!PLACEMENT_REGEX.primary.test(placement[0])) {
  				placement[0] = 'top';
  			}

  			placement[1] = placement[1] || 'center';
  			if (!PLACEMENT_REGEX.secondary.test(placement[1])) {
  				placement[1] = 'center';
  			}

  			if (autoPlace) {
  				placement[2] = true;
  			} else {
  				placement[2] = false;
  			}

  			return placement;
  		},

  		/**
		 * Provides coordinates for an element to be positioned relative to
		 * another element.  Passing 'auto' as part of the placement parameter
		 * will enable smart placement - where the element fits. i.e:
		 * 'auto left-top' will check to see if there is enough space to the left
		 * of the hostElem to fit the targetElem, if not place right (same for secondary
		 * top placement).  Available space is calculated using the viewportOffset
		 * function.
		 *
		 * @param {element} hostElem - The element to position against.
		 * @param {element} targetElem - The element to position.
		 * @param {string=} [placement=top] - The placement for the targetElem,
		 *   default is 'top'. 'center' is assumed as secondary placement for
		 *   'top', 'left', 'right', and 'bottom' placements.  Available placements are:
		 *   <ul>
		 *     <li>top</li>
		 *     <li>top-right</li>
		 *     <li>top-left</li>
		 *     <li>bottom</li>
		 *     <li>bottom-left</li>
		 *     <li>bottom-right</li>
		 *     <li>left</li>
		 *     <li>left-top</li>
		 *     <li>left-bottom</li>
		 *     <li>right</li>
		 *     <li>right-top</li>
		 *     <li>right-bottom</li>
		 *   </ul>
		 * @param {boolean=} [appendToBody=false] - Should the top and left values returned
		 *   be calculated from the body element, default is false.
		 *
		 * @returns {object} An object with the following properties:
		 *   <ul>
		 *     <li>**top**: Value for targetElem top.</li>
		 *     <li>**left**: Value for targetElem left.</li>
		 *     <li>**placement**: The resolved placement.</li>
		 *   </ul>
		 */
  		positionElements: function (hostElem, targetElem, placement, appendToBody) {
  			hostElem = this.getRawNode(hostElem);
  			targetElem = this.getRawNode(targetElem);

  			// need to read from prop to support tests.
  			var targetWidth = angular.isDefined(targetElem.offsetWidth) ? targetElem.offsetWidth : targetElem.prop('offsetWidth');
  			var targetHeight = angular.isDefined(targetElem.offsetHeight) ? targetElem.offsetHeight : targetElem.prop('offsetHeight');

  			placement = this.parsePlacement(placement);

  			var hostElemPos = appendToBody ? this.offset(hostElem) : this.position(hostElem);
  			var targetElemPos = { top: 0, left: 0, placement: '' };

  			if (placement[2]) {
  				var viewportOffset = this.viewportOffset(hostElem, appendToBody);

  				var targetElemStyle = $window.getComputedStyle(targetElem);
  				var adjustedSize = {
  					width: targetWidth + Math.round(Math.abs(this.parseStyle(targetElemStyle.marginLeft) + this.parseStyle(targetElemStyle.marginRight))),
  					height: targetHeight + Math.round(Math.abs(this.parseStyle(targetElemStyle.marginTop) + this.parseStyle(targetElemStyle.marginBottom)))
  				};

  				placement[0] = placement[0] === 'top' && adjustedSize.height > viewportOffset.top && adjustedSize.height <= viewportOffset.bottom ? 'bottom' :
							   placement[0] === 'bottom' && adjustedSize.height > viewportOffset.bottom && adjustedSize.height <= viewportOffset.top ? 'top' :
							   placement[0] === 'left' && adjustedSize.width > viewportOffset.left && adjustedSize.width <= viewportOffset.right ? 'right' :
							   placement[0] === 'right' && adjustedSize.width > viewportOffset.right && adjustedSize.width <= viewportOffset.left ? 'left' :
							   placement[0];

  				placement[1] = placement[1] === 'top' && adjustedSize.height - hostElemPos.height > viewportOffset.bottom && adjustedSize.height - hostElemPos.height <= viewportOffset.top ? 'bottom' :
							   placement[1] === 'bottom' && adjustedSize.height - hostElemPos.height > viewportOffset.top && adjustedSize.height - hostElemPos.height <= viewportOffset.bottom ? 'top' :
							   placement[1] === 'left' && adjustedSize.width - hostElemPos.width > viewportOffset.right && adjustedSize.width - hostElemPos.width <= viewportOffset.left ? 'right' :
							   placement[1] === 'right' && adjustedSize.width - hostElemPos.width > viewportOffset.left && adjustedSize.width - hostElemPos.width <= viewportOffset.right ? 'left' :
							   placement[1];

  				if (placement[1] === 'center') {
  					if (PLACEMENT_REGEX.vertical.test(placement[0])) {
  						var xOverflow = hostElemPos.width / 2 - targetWidth / 2;
  						if (viewportOffset.left + xOverflow < 0 && adjustedSize.width - hostElemPos.width <= viewportOffset.right) {
  							placement[1] = 'left';
  						} else if (viewportOffset.right + xOverflow < 0 && adjustedSize.width - hostElemPos.width <= viewportOffset.left) {
  							placement[1] = 'right';
  						}
  					} else {
  						var yOverflow = hostElemPos.height / 2 - adjustedSize.height / 2;
  						if (viewportOffset.top + yOverflow < 0 && adjustedSize.height - hostElemPos.height <= viewportOffset.bottom) {
  							placement[1] = 'top';
  						} else if (viewportOffset.bottom + yOverflow < 0 && adjustedSize.height - hostElemPos.height <= viewportOffset.top) {
  							placement[1] = 'bottom';
  						}
  					}
  				}
  			}

  			switch (placement[0]) {
  				case 'top':
  					targetElemPos.top = hostElemPos.top - targetHeight;
  					break;
  				case 'bottom':
  					targetElemPos.top = hostElemPos.top + hostElemPos.height;
  					break;
  				case 'left':
  					targetElemPos.left = hostElemPos.left - targetWidth;
  					break;
  				case 'right':
  					targetElemPos.left = hostElemPos.left + hostElemPos.width;
  					break;
  			}

  			switch (placement[1]) {
  				case 'top':
  					targetElemPos.top = hostElemPos.top;
  					break;
  				case 'bottom':
  					targetElemPos.top = hostElemPos.top + hostElemPos.height - targetHeight;
  					break;
  				case 'left':
  					targetElemPos.left = hostElemPos.left;
  					break;
  				case 'right':
  					targetElemPos.left = hostElemPos.left + hostElemPos.width - targetWidth;
  					break;
  				case 'center':
  					if (PLACEMENT_REGEX.vertical.test(placement[0])) {
  						targetElemPos.left = hostElemPos.left + hostElemPos.width / 2 - targetWidth / 2;
  					} else {
  						targetElemPos.top = hostElemPos.top + hostElemPos.height / 2 - targetHeight / 2;
  					}
  					break;
  			}

  			targetElemPos.top = Math.round(targetElemPos.top);
  			targetElemPos.left = Math.round(targetElemPos.left);
  			targetElemPos.placement = placement[1] === 'center' ? placement[0] : placement[0] + '-' + placement[1];

  			return targetElemPos;
  		},

  		/**
		 * Provides a way to adjust the top positioning after first
		 * render to correctly align element to top after content
		 * rendering causes resized element height
		 *
		 * @param {array} placementClasses - The array of strings of classes
		 * element should have.
		 * @param {object} containerPosition - The object with container
		 * position information
		 * @param {number} initialHeight - The initial height for the elem.
		 * @param {number} currentHeight - The current height for the elem.
		 */
  		adjustTop: function (placementClasses, containerPosition, initialHeight, currentHeight) {
  			if (placementClasses.indexOf('top') !== -1 && initialHeight !== currentHeight) {
  				return {
  					top: containerPosition.top - currentHeight + 'px'
  				};
  			}
  		},

  		/**
		 * Provides a way for positioning tooltip & dropdown
		 * arrows when using placement options beyond the standard
		 * left, right, top, or bottom.
		 *
		 * @param {element} elem - The tooltip/dropdown element.
		 * @param {string} placement - The placement for the elem.
		 */
  		positionArrow: function (elem, placement) {
  			elem = this.getRawNode(elem);

  			var innerElem = elem.querySelector('.tooltip-inner, .popover-inner');
  			if (!innerElem) {
  				return;
  			}

  			var isTooltip = angular.element(innerElem).hasClass('tooltip-inner');

  			var arrowElem = isTooltip ? elem.querySelector('.tooltip-arrow') : elem.querySelector('.arrow');
  			if (!arrowElem) {
  				return;
  			}

  			var arrowCss = {
  				top: '',
  				bottom: '',
  				left: '',
  				right: ''
  			};

  			placement = this.parsePlacement(placement);
  			if (placement[1] === 'center') {
  				// no adjustment necessary - just reset styles
  				angular.element(arrowElem).css(arrowCss);
  				return;
  			}

  			var borderProp = 'border-' + placement[0] + '-width';
  			var borderWidth = $window.getComputedStyle(arrowElem)[borderProp];

  			var borderRadiusProp = 'border-';
  			if (PLACEMENT_REGEX.vertical.test(placement[0])) {
  				borderRadiusProp += placement[0] + '-' + placement[1];
  			} else {
  				borderRadiusProp += placement[1] + '-' + placement[0];
  			}
  			borderRadiusProp += '-radius';
  			var borderRadius = $window.getComputedStyle(isTooltip ? innerElem : elem)[borderRadiusProp];

  			switch (placement[0]) {
  				case 'top':
  					arrowCss.bottom = isTooltip ? '0' : '-' + borderWidth;
  					break;
  				case 'bottom':
  					arrowCss.top = isTooltip ? '0' : '-' + borderWidth;
  					break;
  				case 'left':
  					arrowCss.right = isTooltip ? '0' : '-' + borderWidth;
  					break;
  				case 'right':
  					arrowCss.left = isTooltip ? '0' : '-' + borderWidth;
  					break;
  			}

  			arrowCss[placement[1]] = borderRadius;

  			angular.element(arrowElem).css(arrowCss);
  		}
  	};
  }]);

angular.module('ui.bootstrap.datepickerPopup', ['ui.bootstrap.datepicker', 'ui.bootstrap.position'])

.value('$datepickerPopupLiteralWarning', true)

.constant('uibDatepickerPopupConfig', {
	altInputFormats: [],
	appendToBody: false,
	clearText: 'Clear',
	closeOnDateSelection: true,
	closeText: 'Done',
	currentText: 'Today',
	datepickerPopup: 'yyyy-MM-dd',
	datepickerPopupTemplateUrl: 'uib/template/datepickerPopup/popup.html',
	datepickerTemplateUrl: 'uib/template/datepicker/datepicker.html',
	html5Types: {
		date: 'yyyy-MM-dd',
		'datetime-local': 'yyyy-MM-ddTHH:mm:ss.sss',
		'month': 'yyyy-MM'
	},
	onOpenFocus: true,
	showButtonBar: true,
	placement: 'auto bottom-left'
})

.controller('UibDatepickerPopupController', ['$scope', '$element', '$attrs', '$compile', '$log', '$parse', '$window', '$document', '$rootScope', '$uibPosition', 'dateFilter', 'uibDateParser', 'uibDatepickerPopupConfig', '$timeout', 'uibDatepickerConfig', '$datepickerPopupLiteralWarning',
function ($scope, $element, $attrs, $compile, $log, $parse, $window, $document, $rootScope, $position, dateFilter, dateParser, datepickerPopupConfig, $timeout, datepickerConfig, $datepickerPopupLiteralWarning) {
	var cache = {},
	  isHtml5DateInput = false;
	var dateFormat, closeOnDateSelection, appendToBody, onOpenFocus,
	  datepickerPopupTemplateUrl, datepickerTemplateUrl, popupEl, datepickerEl, scrollParentEl,
	  ngModel, ngModelOptions, $popup, altInputFormats, watchListeners = [];

	this.init = function (_ngModel_) {
		ngModel = _ngModel_;
		ngModelOptions = angular.isObject(_ngModel_.$options) ?
		  _ngModel_.$options :
      {
      	timezone: null
      };
		closeOnDateSelection = angular.isDefined($attrs.closeOnDateSelection) ?
		  $scope.$parent.$eval($attrs.closeOnDateSelection) :
		  datepickerPopupConfig.closeOnDateSelection;
		appendToBody = angular.isDefined($attrs.datepickerAppendToBody) ?
		  $scope.$parent.$eval($attrs.datepickerAppendToBody) :
		  datepickerPopupConfig.appendToBody;
		onOpenFocus = angular.isDefined($attrs.onOpenFocus) ?
		  $scope.$parent.$eval($attrs.onOpenFocus) : datepickerPopupConfig.onOpenFocus;
		datepickerPopupTemplateUrl = angular.isDefined($attrs.datepickerPopupTemplateUrl) ?
		  $attrs.datepickerPopupTemplateUrl :
		  datepickerPopupConfig.datepickerPopupTemplateUrl;
		datepickerTemplateUrl = angular.isDefined($attrs.datepickerTemplateUrl) ?
		  $attrs.datepickerTemplateUrl : datepickerPopupConfig.datepickerTemplateUrl;
		altInputFormats = angular.isDefined($attrs.altInputFormats) ?
		  $scope.$parent.$eval($attrs.altInputFormats) :
		  datepickerPopupConfig.altInputFormats;

		$scope.showButtonBar = angular.isDefined($attrs.showButtonBar) ?
		  $scope.$parent.$eval($attrs.showButtonBar) :
		  datepickerPopupConfig.showButtonBar;

		if (datepickerPopupConfig.html5Types[$attrs.type]) {
			dateFormat = datepickerPopupConfig.html5Types[$attrs.type];
			isHtml5DateInput = true;
		} else {
			dateFormat = $attrs.uibDatepickerPopup || datepickerPopupConfig.datepickerPopup;
			$attrs.$observe('uibDatepickerPopup', function (value, oldValue) {
				var newDateFormat = value || datepickerPopupConfig.datepickerPopup;
				// Invalidate the $modelValue to ensure that formatters re-run
				// FIXME: Refactor when PR is merged: https://github.com/angular/angular.js/pull/10764
				if (newDateFormat !== dateFormat) {
					dateFormat = newDateFormat;
					ngModel.$modelValue = null;

					if (!dateFormat) {
						throw new Error('uibDatepickerPopup must have a date format specified.');
					}
				}
			});
		}

		if (!dateFormat) {
			throw new Error('uibDatepickerPopup must have a date format specified.');
		}

		if (isHtml5DateInput && $attrs.uibDatepickerPopup) {
			throw new Error('HTML5 date input types do not support custom formats.');
		}

		// popup element used to display calendar
		popupEl = angular.element('<div uib-datepicker-popup-wrap><div uib-datepicker></div></div>');

		popupEl.attr({
			'ng-model': 'date',
			'ng-change': 'dateSelection(date)',
			'template-url': datepickerPopupTemplateUrl
		});

		// datepicker element
		datepickerEl = angular.element(popupEl.children()[0]);
		datepickerEl.attr('template-url', datepickerTemplateUrl);

		if (!$scope.datepickerOptions) {
			$scope.datepickerOptions = {};
		}

		if (isHtml5DateInput) {
			if ($attrs.type === 'month') {
				$scope.datepickerOptions.datepickerMode = 'month';
				$scope.datepickerOptions.minMode = 'month';
			}
		}

		datepickerEl.attr('datepicker-options', 'datepickerOptions');

		if (!isHtml5DateInput) {
			// Internal API to maintain the correct ng-invalid-[key] class
			ngModel.$$parserName = 'date';
			ngModel.$validators.date = validator;
			ngModel.$parsers.unshift(parseDate);
			ngModel.$formatters.push(function (value) {
				if (ngModel.$isEmpty(value)) {
					$scope.date = value;
					return value;
				}

				if (angular.isNumber(value)) {
					value = new Date(value);
				}

				$scope.date = dateParser.fromTimezone(value, ngModelOptions.timezone);

				return dateParser.filter($scope.date, dateFormat);
			});
		} else {
			ngModel.$formatters.push(function (value) {
				$scope.date = dateParser.fromTimezone(value, ngModelOptions.timezone);
				return value;
			});
		}

		// Detect changes in the view from the text box
		ngModel.$viewChangeListeners.push(function () {
			$scope.date = parseDateString(ngModel.$viewValue);
		});

		$element.on('keydown', inputKeydownBind);

		$popup = $compile(popupEl)($scope);
		// Prevent jQuery cache memory leak (template is now redundant after linking)
		popupEl.remove();

		if (appendToBody) {
			$document.find('body').append($popup);
		} else {
			$element.after($popup);
		}

		$scope.$on('$destroy', function () {
			if ($scope.isOpen === true) {
				if (!$rootScope.$$phase) {
					$scope.$apply(function () {
						$scope.isOpen = false;
					});
				}
			}

			$popup.remove();
			$element.off('keydown', inputKeydownBind);
			$document.off('click', documentClickBind);
			if (scrollParentEl) {
				scrollParentEl.off('scroll', positionPopup);
			}
			angular.element($window).off('resize', positionPopup);

			//Clear all watch listeners on destroy
			while (watchListeners.length) {
				watchListeners.shift()();
			}
		});
	};

	$scope.getText = function (key) {
		return $scope[key + 'Text'] || datepickerPopupConfig[key + 'Text'];
	};

	$scope.isDisabled = function (date) {
		if (date === 'today') {
			date = dateParser.fromTimezone(new Date(), ngModelOptions.timezone);
		}

		var dates = {};
		angular.forEach(['minDate', 'maxDate'], function (key) {
			if (!$scope.datepickerOptions[key]) {
				dates[key] = null;
			} else if (angular.isDate($scope.datepickerOptions[key])) {
				dates[key] = new Date($scope.datepickerOptions[key]);
			} else {
				if ($datepickerPopupLiteralWarning) {
					$log.warn('Literal date support has been deprecated, please switch to date object usage');
				}

				dates[key] = new Date(dateFilter($scope.datepickerOptions[key], 'medium'));
			}
		});

		return $scope.datepickerOptions &&
		  dates.minDate && $scope.compare(date, dates.minDate) < 0 ||
		  dates.maxDate && $scope.compare(date, dates.maxDate) > 0;
	};

	$scope.compare = function (date1, date2) {
		return new Date(date1.getFullYear(), date1.getMonth(), date1.getDate()) - new Date(date2.getFullYear(), date2.getMonth(), date2.getDate());
	};

	// Inner change
	$scope.dateSelection = function (dt) {
		$scope.date = dt;
		var date = $scope.date ? dateParser.filter($scope.date, dateFormat) : null; // Setting to NULL is necessary for form validators to function
		$element.val(date);
		ngModel.$setViewValue(date);

		if (closeOnDateSelection) {
			$scope.isOpen = false;
			$element[0].focus();
		}
	};

	$scope.keydown = function (evt) {
		if (evt.which === 27) {
			evt.stopPropagation();
			$scope.isOpen = false;
			$element[0].focus();
		}
	};

	$scope.select = function (date, evt) {
		evt.stopPropagation();

		if (date === 'today') {
			var today = new Date();
			if (angular.isDate($scope.date)) {
				date = new Date($scope.date);
				date.setFullYear(today.getFullYear(), today.getMonth(), today.getDate());
			} else {
				date = dateParser.fromTimezone(today, ngModelOptions.timezone);
				date.setHours(0, 0, 0, 0);
			}
		}
		$scope.dateSelection(date);
	};

	$scope.close = function (evt) {
		evt.stopPropagation();

		$scope.isOpen = false;
		$element[0].focus();
	};

	$scope.disabled = angular.isDefined($attrs.disabled) || false;
	if ($attrs.ngDisabled) {
		watchListeners.push($scope.$parent.$watch($parse($attrs.ngDisabled), function (disabled) {
			$scope.disabled = disabled;
		}));
	}

	$scope.$watch('isOpen', function (value) {
		if (value) {
			if (!$scope.disabled) {
				$timeout(function () {
					positionPopup();

					if (onOpenFocus) {
						$scope.$broadcast('uib:datepicker.focus');
					}

					$document.on('click', documentClickBind);

					var placement = $attrs.popupPlacement ? $attrs.popupPlacement : datepickerPopupConfig.placement;
					if (appendToBody || $position.parsePlacement(placement)[2]) {
						scrollParentEl = scrollParentEl || angular.element($position.scrollParent($element));
						if (scrollParentEl) {
							scrollParentEl.on('scroll', positionPopup);
						}
					} else {
						scrollParentEl = null;
					}

					angular.element($window).on('resize', positionPopup);
				}, 0, false);
			} else {
				$scope.isOpen = false;
			}
		} else {
			$document.off('click', documentClickBind);
			if (scrollParentEl) {
				scrollParentEl.off('scroll', positionPopup);
			}
			angular.element($window).off('resize', positionPopup);
		}
	});

	function cameltoDash(string) {
		return string.replace(/([A-Z])/g, function ($1) { return '-' + $1.toLowerCase(); });
	}

	function parseDateString(viewValue) {
		var date = dateParser.parse(viewValue, dateFormat, $scope.date);
		if (isNaN(date)) {
			for (var i = 0; i < altInputFormats.length; i++) {
				date = dateParser.parse(viewValue, altInputFormats[i], $scope.date);
				if (!isNaN(date)) {
					return date;
				}
			}
		}
		return date;
	}

	function parseDate(viewValue) {
		if (angular.isNumber(viewValue)) {
			// presumably timestamp to date object
			viewValue = new Date(viewValue);
		}

		if (!viewValue) {
			return null;
		}

		if (angular.isDate(viewValue) && !isNaN(viewValue)) {
			return viewValue;
		}

		if (angular.isString(viewValue)) {
			var date = parseDateString(viewValue);
			if (!isNaN(date)) {
				return dateParser.fromTimezone(date, ngModelOptions.timezone);
			}
		}

		return ngModel.$options && ngModel.$options.allowInvalid ? viewValue : undefined;
	}

	function validator(modelValue, viewValue) {
		var value = modelValue || viewValue;

		if (!$attrs.ngRequired && !value) {
			return true;
		}

		if (angular.isNumber(value)) {
			value = new Date(value);
		}

		if (!value) {
			return true;
		}

		if (angular.isDate(value) && !isNaN(value)) {
			return true;
		}

		if (angular.isString(value)) {
			return !isNaN(parseDateString(value));
		}

		return false;
	}

	function documentClickBind(event) {
		if (!$scope.isOpen && $scope.disabled) {
			return;
		}

		var popup = $popup[0];
		var dpContainsTarget = $element[0].contains(event.target);
		// The popup node may not be an element node
		// In some browsers (IE) only element nodes have the 'contains' function
		var popupContainsTarget = popup.contains !== undefined && popup.contains(event.target);
		if ($scope.isOpen && !(dpContainsTarget || popupContainsTarget)) {
			$scope.$apply(function () {
				$scope.isOpen = false;
			});
		}
	}

	function inputKeydownBind(evt) {
		if (evt.which === 27 && $scope.isOpen) {
			evt.preventDefault();
			evt.stopPropagation();
			$scope.$apply(function () {
				$scope.isOpen = false;
			});
			$element[0].focus();
		} else if (evt.which === 40 && !$scope.isOpen) {
			evt.preventDefault();
			evt.stopPropagation();
			$scope.$apply(function () {
				$scope.isOpen = true;
			});
		}
	}

	function positionPopup() {
		if ($scope.isOpen) {
			var dpElement = angular.element($popup[0].querySelector('.uib-datepicker-popup'));
			var placement = $attrs.popupPlacement ? $attrs.popupPlacement : datepickerPopupConfig.placement;
			var position = $position.positionElements($element, dpElement, placement, appendToBody);
			dpElement.css({ top: position.top + 'px', left: position.left + 'px' });
			if (dpElement.hasClass('uib-position-measure')) {
				dpElement.removeClass('uib-position-measure');
			}
		}
	}

	$scope.$on('uib:datepicker.mode', function () {
		$timeout(positionPopup, 0, false);
	});
}])

.directive('uibDatepickerPopup', function () {
	return {
		require: ['ngModel', 'uibDatepickerPopup'],
		controller: 'UibDatepickerPopupController',
		scope: {
			datepickerOptions: '=?',
			isOpen: '=?',
			currentText: '@',
			clearText: '@',
			closeText: '@'
		},
		link: function (scope, element, attrs, ctrls) {
			var ngModel = ctrls[0],
			  ctrl = ctrls[1];

			ctrl.init(ngModel);
		}
	};
})

.directive('uibDatepickerPopupWrap', function () {
	return {
		restrict: 'A',
		transclude: true,
		templateUrl: function (element, attrs) {
			return attrs.templateUrl || 'uib/template/datepickerPopup/popup.html';
		}
	};
});

angular.module('ui.bootstrap.debounce', [])
/**
 * A helper, internal service that debounces a function
 */
  .factory('$$debounce', ['$timeout', function ($timeout) {
  	return function (callback, debounceTime) {
  		var timeoutPromise;

  		return function () {
  			var self = this;
  			var args = Array.prototype.slice.call(arguments);
  			if (timeoutPromise) {
  				$timeout.cancel(timeoutPromise);
  			}

  			timeoutPromise = $timeout(function () {
  				callback.apply(self, args);
  			}, debounceTime);
  		};
  	};
  }]);

angular.module('ui.bootstrap.dropdown', ['ui.bootstrap.position'])

.constant('uibDropdownConfig', {
	appendToOpenClass: 'uib-dropdown-open',
	openClass: 'open'
})

.service('uibDropdownService', ['$document', '$rootScope', function ($document, $rootScope) {
	var openScope = null;

	this.open = function (dropdownScope, element) {
		if (!openScope) {
			$document.on('click', closeDropdown);
		}

		if (openScope && openScope !== dropdownScope) {
			openScope.isOpen = false;
		}

		openScope = dropdownScope;
	};

	this.close = function (dropdownScope, element) {
		if (openScope === dropdownScope) {
			openScope = null;
			$document.off('click', closeDropdown);
			$document.off('keydown', this.keybindFilter);
		}
	};

	var closeDropdown = function (evt) {
		// This method may still be called during the same mouse event that
		// unbound this event handler. So check openScope before proceeding.
		if (!openScope) { return; }

		if (evt && openScope.getAutoClose() === 'disabled') { return; }

		if (evt && evt.which === 3) { return; }

		var toggleElement = openScope.getToggleElement();
		if (evt && toggleElement && toggleElement[0].contains(evt.target)) {
			return;
		}

		var dropdownElement = openScope.getDropdownElement();
		if (evt && openScope.getAutoClose() === 'outsideClick' &&
		  dropdownElement && dropdownElement[0].contains(evt.target)) {
			return;
		}

		openScope.isOpen = false;
		openScope.focusToggleElement();

		if (!$rootScope.$$phase) {
			openScope.$apply();
		}
	};

	this.keybindFilter = function (evt) {
		var dropdownElement = openScope.getDropdownElement();
		var toggleElement = openScope.getToggleElement();
		var dropdownElementTargeted = dropdownElement && dropdownElement[0].contains(evt.target);
		var toggleElementTargeted = toggleElement && toggleElement[0].contains(evt.target);
		if (evt.which === 27) {
			evt.stopPropagation();
			openScope.focusToggleElement();
			closeDropdown();
		} else if (openScope.isKeynavEnabled() && [38, 40].indexOf(evt.which) !== -1 && openScope.isOpen && (dropdownElementTargeted || toggleElementTargeted)) {
			evt.preventDefault();
			evt.stopPropagation();
			openScope.focusDropdownEntry(evt.which);
		}
	};
}])

.controller('UibDropdownController', ['$scope', '$element', '$attrs', '$parse', 'uibDropdownConfig', 'uibDropdownService', '$animate', '$uibPosition', '$document', '$compile', '$templateRequest', function ($scope, $element, $attrs, $parse, dropdownConfig, uibDropdownService, $animate, $position, $document, $compile, $templateRequest) {
	var self = this,
	  scope = $scope.$new(), // create a child scope so we are not polluting original one
	  templateScope,
	  appendToOpenClass = dropdownConfig.appendToOpenClass,
	  openClass = dropdownConfig.openClass,
	  getIsOpen,
	  setIsOpen = angular.noop,
	  toggleInvoker = $attrs.onToggle ? $parse($attrs.onToggle) : angular.noop,
	  appendToBody = false,
	  appendTo = null,
	  keynavEnabled = false,
	  selectedOption = null,
	  body = $document.find('body');

	$element.addClass('dropdown');

	this.init = function () {
		if ($attrs.isOpen) {
			getIsOpen = $parse($attrs.isOpen);
			setIsOpen = getIsOpen.assign;

			$scope.$watch(getIsOpen, function (value) {
				scope.isOpen = !!value;
			});
		}

		if (angular.isDefined($attrs.dropdownAppendTo)) {
			var appendToEl = $parse($attrs.dropdownAppendTo)(scope);
			if (appendToEl) {
				appendTo = angular.element(appendToEl);
			}
		}

		appendToBody = angular.isDefined($attrs.dropdownAppendToBody);
		keynavEnabled = angular.isDefined($attrs.keyboardNav);

		if (appendToBody && !appendTo) {
			appendTo = body;
		}

		if (appendTo && self.dropdownMenu) {
			appendTo.append(self.dropdownMenu);
			$element.on('$destroy', function handleDestroyEvent() {
				self.dropdownMenu.remove();
			});
		}
	};

	this.toggle = function (open) {
		scope.isOpen = arguments.length ? !!open : !scope.isOpen;
		if (angular.isFunction(setIsOpen)) {
			setIsOpen(scope, scope.isOpen);
		}

		return scope.isOpen;
	};

	// Allow other directives to watch status
	this.isOpen = function () {
		return scope.isOpen;
	};

	scope.getToggleElement = function () {
		return self.toggleElement;
	};

	scope.getAutoClose = function () {
		return $attrs.autoClose || 'always'; //or 'outsideClick' or 'disabled'
	};

	scope.getElement = function () {
		return $element;
	};

	scope.isKeynavEnabled = function () {
		return keynavEnabled;
	};

	scope.focusDropdownEntry = function (keyCode) {
		var elems = self.dropdownMenu ? //If append to body is used.
		  angular.element(self.dropdownMenu).find('a') :
		  $element.find('ul').eq(0).find('a');

		switch (keyCode) {
			case 40: {
				if (!angular.isNumber(self.selectedOption)) {
					self.selectedOption = 0;
				} else {
					self.selectedOption = self.selectedOption === elems.length - 1 ?
					  self.selectedOption :
					  self.selectedOption + 1;
				}
				break;
			}
			case 38: {
				if (!angular.isNumber(self.selectedOption)) {
					self.selectedOption = elems.length - 1;
				} else {
					self.selectedOption = self.selectedOption === 0 ?
					  0 : self.selectedOption - 1;
				}
				break;
			}
		}
		elems[self.selectedOption].focus();
	};

	scope.getDropdownElement = function () {
		return self.dropdownMenu;
	};

	scope.focusToggleElement = function () {
		if (self.toggleElement) {
			self.toggleElement[0].focus();
		}
	};

	scope.$watch('isOpen', function (isOpen, wasOpen) {
		if (appendTo && self.dropdownMenu) {
			var pos = $position.positionElements($element, self.dropdownMenu, 'bottom-left', true),
			  css,
			  rightalign,
			  scrollbarPadding,
			  scrollbarWidth = 0;

			css = {
				top: pos.top + 'px',
				display: isOpen ? 'block' : 'none'
			};

			rightalign = self.dropdownMenu.hasClass('dropdown-menu-right');
			if (!rightalign) {
				css.left = pos.left + 'px';
				css.right = 'auto';
			} else {
				css.left = 'auto';
				scrollbarPadding = $position.scrollbarPadding(appendTo);

				if (scrollbarPadding.heightOverflow && scrollbarPadding.scrollbarWidth) {
					scrollbarWidth = scrollbarPadding.scrollbarWidth;
				}

				css.right = window.innerWidth - scrollbarWidth -
				  (pos.left + $element.prop('offsetWidth')) + 'px';
			}

			// Need to adjust our positioning to be relative to the appendTo container
			// if it's not the body element
			if (!appendToBody) {
				var appendOffset = $position.offset(appendTo);

				css.top = pos.top - appendOffset.top + 'px';

				if (!rightalign) {
					css.left = pos.left - appendOffset.left + 'px';
				} else {
					css.right = window.innerWidth -
					  (pos.left - appendOffset.left + $element.prop('offsetWidth')) + 'px';
				}
			}

			self.dropdownMenu.css(css);
		}

		var openContainer = appendTo ? appendTo : $element;
		var hasOpenClass = openContainer.hasClass(appendTo ? appendToOpenClass : openClass);

		if (hasOpenClass === !isOpen) {
			$animate[isOpen ? 'addClass' : 'removeClass'](openContainer, appendTo ? appendToOpenClass : openClass).then(function () {
				if (angular.isDefined(isOpen) && isOpen !== wasOpen) {
					toggleInvoker($scope, { open: !!isOpen });
				}
			});
		}

		if (isOpen) {
			if (self.dropdownMenuTemplateUrl) {
				$templateRequest(self.dropdownMenuTemplateUrl).then(function (tplContent) {
					templateScope = scope.$new();
					$compile(tplContent.trim())(templateScope, function (dropdownElement) {
						var newEl = dropdownElement;
						self.dropdownMenu.replaceWith(newEl);
						self.dropdownMenu = newEl;
						$document.on('keydown', uibDropdownService.keybindFilter);
					});
				});
			} else {
				$document.on('keydown', uibDropdownService.keybindFilter);
			}

			scope.focusToggleElement();
			uibDropdownService.open(scope, $element);
		} else {
			uibDropdownService.close(scope, $element);
			if (self.dropdownMenuTemplateUrl) {
				if (templateScope) {
					templateScope.$destroy();
				}
				var newEl = angular.element('<ul class="dropdown-menu"></ul>');
				self.dropdownMenu.replaceWith(newEl);
				self.dropdownMenu = newEl;
			}

			self.selectedOption = null;
		}

		if (angular.isFunction(setIsOpen)) {
			setIsOpen($scope, isOpen);
		}
	});
}])

.directive('uibDropdown', function () {
	return {
		controller: 'UibDropdownController',
		link: function (scope, element, attrs, dropdownCtrl) {
			dropdownCtrl.init();
		}
	};
})

.directive('uibDropdownMenu', function () {
	return {
		restrict: 'A',
		require: '?^uibDropdown',
		link: function (scope, element, attrs, dropdownCtrl) {
			if (!dropdownCtrl || angular.isDefined(attrs.dropdownNested)) {
				return;
			}

			element.addClass('dropdown-menu');

			var tplUrl = attrs.templateUrl;
			if (tplUrl) {
				dropdownCtrl.dropdownMenuTemplateUrl = tplUrl;
			}

			if (!dropdownCtrl.dropdownMenu) {
				dropdownCtrl.dropdownMenu = element;
			}
		}
	};
})

.directive('uibDropdownToggle', function () {
	return {
		require: '?^uibDropdown',
		link: function (scope, element, attrs, dropdownCtrl) {
			if (!dropdownCtrl) {
				return;
			}

			element.addClass('dropdown-toggle');

			dropdownCtrl.toggleElement = element;

			var toggleDropdown = function (event) {
				event.preventDefault();

				if (!element.hasClass('disabled') && !attrs.disabled) {
					scope.$apply(function () {
						dropdownCtrl.toggle();
					});
				}
			};

			element.bind('click', toggleDropdown);

			// WAI-ARIA
			element.attr({ 'aria-haspopup': true, 'aria-expanded': false });
			scope.$watch(dropdownCtrl.isOpen, function (isOpen) {
				element.attr('aria-expanded', !!isOpen);
			});

			scope.$on('$destroy', function () {
				element.unbind('click', toggleDropdown);
			});
		}
	};
});

angular.module('ui.bootstrap.stackedMap', [])
/**
 * A helper, internal data structure that acts as a map but also allows getting / removing
 * elements in the LIFO order
 */
  .factory('$$stackedMap', function () {
  	return {
  		createNew: function () {
  			var stack = [];

  			return {
  				add: function (key, value) {
  					stack.push({
  						key: key,
  						value: value
  					});
  				},
  				get: function (key) {
  					for (var i = 0; i < stack.length; i++) {
  						if (key === stack[i].key) {
  							return stack[i];
  						}
  					}
  				},
  				keys: function () {
  					var keys = [];
  					for (var i = 0; i < stack.length; i++) {
  						keys.push(stack[i].key);
  					}
  					return keys;
  				},
  				top: function () {
  					return stack[stack.length - 1];
  				},
  				remove: function (key) {
  					var idx = -1;
  					for (var i = 0; i < stack.length; i++) {
  						if (key === stack[i].key) {
  							idx = i;
  							break;
  						}
  					}
  					return stack.splice(idx, 1)[0];
  				},
  				removeTop: function () {
  					return stack.pop();
  				},
  				length: function () {
  					return stack.length;
  				}
  			};
  		}
  	};
  });
angular.module('ui.bootstrap.modal', ['ui.bootstrap.stackedMap', 'ui.bootstrap.position'])
/**
 * A helper, internal data structure that stores all references attached to key
 */
  .factory('$$multiMap', function () {
  	return {
  		createNew: function () {
  			var map = {};

  			return {
  				entries: function () {
  					return Object.keys(map).map(function (key) {
  						return {
  							key: key,
  							value: map[key]
  						};
  					});
  				},
  				get: function (key) {
  					return map[key];
  				},
  				hasKey: function (key) {
  					return !!map[key];
  				},
  				keys: function () {
  					return Object.keys(map);
  				},
  				put: function (key, value) {
  					if (!map[key]) {
  						map[key] = [];
  					}

  					map[key].push(value);
  				},
  				remove: function (key, value) {
  					var values = map[key];

  					if (!values) {
  						return;
  					}

  					var idx = values.indexOf(value);

  					if (idx !== -1) {
  						values.splice(idx, 1);
  					}

  					if (!values.length) {
  						delete map[key];
  					}
  				}
  			};
  		}
  	};
  })

/**
 * Pluggable resolve mechanism for the modal resolve resolution
 * Supports UI Router's $resolve service
 */
  .provider('$uibResolve', function () {
  	var resolve = this;
  	this.resolver = null;

  	this.setResolver = function (resolver) {
  		this.resolver = resolver;
  	};

  	this.$get = ['$injector', '$q', function ($injector, $q) {
  		var resolver = resolve.resolver ? $injector.get(resolve.resolver) : null;
  		return {
  			resolve: function (invocables, locals, parent, self) {
  				if (resolver) {
  					return resolver.resolve(invocables, locals, parent, self);
  				}

  				var promises = [];

  				angular.forEach(invocables, function (value) {
  					if (angular.isFunction(value) || angular.isArray(value)) {
  						promises.push($q.resolve($injector.invoke(value)));
  					} else if (angular.isString(value)) {
  						promises.push($q.resolve($injector.get(value)));
  					} else {
  						promises.push($q.resolve(value));
  					}
  				});

  				return $q.all(promises).then(function (resolves) {
  					var resolveObj = {};
  					var resolveIter = 0;
  					angular.forEach(invocables, function (value, key) {
  						resolveObj[key] = resolves[resolveIter++];
  					});

  					return resolveObj;
  				});
  			}
  		};
  	}];
  })

/**
 * A helper directive for the $modal service. It creates a backdrop element.
 */
  .directive('uibModalBackdrop', ['$animate', '$injector', '$uibModalStack',
  function ($animate, $injector, $modalStack) {
  	return {
  		restrict: 'A',
  		compile: function (tElement, tAttrs) {
  			tElement.addClass(tAttrs.backdropClass);
  			return linkFn;
  		}
  	};

  	function linkFn(scope, element, attrs) {
  		if (attrs.modalInClass) {
  			$animate.addClass(element, attrs.modalInClass);

  			scope.$on($modalStack.NOW_CLOSING_EVENT, function (e, setIsAsync) {
  				var done = setIsAsync();
  				if (scope.modalOptions.animation) {
  					$animate.removeClass(element, attrs.modalInClass).then(done);
  				} else {
  					done();
  				}
  			});
  		}
  	}
  }])

  .directive('uibModalWindow', ['$uibModalStack', '$q', '$animateCss', '$document',
  function ($modalStack, $q, $animateCss, $document) {
  	return {
  		scope: {
  			index: '@'
  		},
  		restrict: 'A',
  		transclude: true,
  		templateUrl: function (tElement, tAttrs) {
  			return tAttrs.templateUrl || 'uib/template/modal/window.html';
  		},
  		link: function (scope, element, attrs) {
  			element.addClass(attrs.windowTopClass || '');
  			scope.size = attrs.size;

  			scope.close = function (evt) {
  				var modal = $modalStack.getTop();
  				if (modal && modal.value.backdrop &&
				  modal.value.backdrop !== 'static' &&
				  evt.target === evt.currentTarget) {
  					evt.preventDefault();
  					evt.stopPropagation();
  					$modalStack.dismiss(modal.key, 'backdrop click');
  				}
  			};

  			// moved from template to fix issue #2280
  			element.on('click', scope.close);

  			// This property is only added to the scope for the purpose of detecting when this directive is rendered.
  			// We can detect that by using this property in the template associated with this directive and then use
  			// {@link Attribute#$observe} on it. For more details please see {@link TableColumnResize}.
  			scope.$isRendered = true;

  			// Deferred object that will be resolved when this modal is render.
  			var modalRenderDeferObj = $q.defer();
  			// Resolve render promise post-digest
  			scope.$$postDigest(function () {
  				modalRenderDeferObj.resolve();
  			});

  			modalRenderDeferObj.promise.then(function () {
  				var animationPromise = null;

  				if (attrs.modalInClass) {
  					animationPromise = $animateCss(element, {
  						addClass: attrs.modalInClass
  					}).start();

  					scope.$on($modalStack.NOW_CLOSING_EVENT, function (e, setIsAsync) {
  						var done = setIsAsync();
  						$animateCss(element, {
  							removeClass: attrs.modalInClass
  						}).start().then(done);
  					});
  				}


  				$q.when(animationPromise).then(function () {
  					// Notify {@link $modalStack} that modal is rendered.
  					var modal = $modalStack.getTop();
  					if (modal) {
  						$modalStack.modalRendered(modal.key);
  					}

  					/**
					 * If something within the freshly-opened modal already has focus (perhaps via a
					 * directive that causes focus). then no need to try and focus anything.
					 */
  					if (!($document[0].activeElement && element[0].contains($document[0].activeElement))) {
  						var inputWithAutofocus = element[0].querySelector('[autofocus]');
  						/**
						 * Auto-focusing of a freshly-opened modal element causes any child elements
						 * with the autofocus attribute to lose focus. This is an issue on touch
						 * based devices which will show and then hide the onscreen keyboard.
						 * Attempts to refocus the autofocus element via JavaScript will not reopen
						 * the onscreen keyboard. Fixed by updated the focusing logic to only autofocus
						 * the modal element if the modal does not contain an autofocus element.
						 */
  						if (inputWithAutofocus) {
  							inputWithAutofocus.focus();
  						} else {
  							element[0].focus();
  						}
  					}
  				});
  			});
  		}
  	};
  }])

  .directive('uibModalAnimationClass', function () {
  	return {
  		compile: function (tElement, tAttrs) {
  			if (tAttrs.modalAnimation) {
  				tElement.addClass(tAttrs.uibModalAnimationClass);
  			}
  		}
  	};
  })

  .directive('uibModalTransclude', ['$animate', function ($animate) {
  	return {
  		link: function (scope, element, attrs, controller, transclude) {
  			transclude(scope.$parent, function (clone) {
  				element.empty();
  				$animate.enter(clone, element);
  			});
  		}
  	};
  }])

  .factory('$uibModalStack', ['$animate', '$animateCss', '$document',
    '$compile', '$rootScope', '$q', '$$multiMap', '$$stackedMap', '$uibPosition',
    function ($animate, $animateCss, $document, $compile, $rootScope, $q, $$multiMap, $$stackedMap, $uibPosition) {
    	var OPENED_MODAL_CLASS = 'modal-open';

    	var backdropDomEl, backdropScope;
    	var openedWindows = $$stackedMap.createNew();
    	var openedClasses = $$multiMap.createNew();
    	var $modalStack = {
    		NOW_CLOSING_EVENT: 'modal.stack.now-closing'
    	};
    	var topModalIndex = 0;
    	var previousTopOpenedModal = null;

    	//Modal focus behavior
    	var tabbableSelector = 'a[href], area[href], input:not([disabled]):not([tabindex=\'-1\']), ' +
		  'button:not([disabled]):not([tabindex=\'-1\']),select:not([disabled]):not([tabindex=\'-1\']), textarea:not([disabled]):not([tabindex=\'-1\']), ' +
		  'iframe, object, embed, *[tabindex]:not([tabindex=\'-1\']), *[contenteditable=true]';
    	var scrollbarPadding;
    	var SNAKE_CASE_REGEXP = /[A-Z]/g;

    	// TODO: extract into common dependency with tooltip
    	function snake_case(name) {
    		var separator = '-';
    		return name.replace(SNAKE_CASE_REGEXP, function (letter, pos) {
    			return (pos ? separator : '') + letter.toLowerCase();
    		});
    	}

    	function isVisible(element) {
    		return !!(element.offsetWidth ||
			  element.offsetHeight ||
			  element.getClientRects().length);
    	}

    	function backdropIndex() {
    		var topBackdropIndex = -1;
    		var opened = openedWindows.keys();
    		for (var i = 0; i < opened.length; i++) {
    			if (openedWindows.get(opened[i]).value.backdrop) {
    				topBackdropIndex = i;
    			}
    		}

    		// If any backdrop exist, ensure that it's index is always
    		// right below the top modal
    		if (topBackdropIndex > -1 && topBackdropIndex < topModalIndex) {
    			topBackdropIndex = topModalIndex;
    		}
    		return topBackdropIndex;
    	}

    	$rootScope.$watch(backdropIndex, function (newBackdropIndex) {
    		if (backdropScope) {
    			backdropScope.index = newBackdropIndex;
    		}
    	});

    	function removeModalWindow(modalInstance, elementToReceiveFocus) {
    		var modalWindow = openedWindows.get(modalInstance).value;
    		var appendToElement = modalWindow.appendTo;

    		//clean up the stack
    		openedWindows.remove(modalInstance);
    		previousTopOpenedModal = openedWindows.top();
    		if (previousTopOpenedModal) {
    			topModalIndex = parseInt(previousTopOpenedModal.value.modalDomEl.attr('index'), 10);
    		}

    		removeAfterAnimate(modalWindow.modalDomEl, modalWindow.modalScope, function () {
    			var modalBodyClass = modalWindow.openedClass || OPENED_MODAL_CLASS;
    			openedClasses.remove(modalBodyClass, modalInstance);
    			var areAnyOpen = openedClasses.hasKey(modalBodyClass);
    			appendToElement.toggleClass(modalBodyClass, areAnyOpen);
    			if (!areAnyOpen && scrollbarPadding && scrollbarPadding.heightOverflow && scrollbarPadding.scrollbarWidth) {
    				if (scrollbarPadding.originalRight) {
    					appendToElement.css({ paddingRight: scrollbarPadding.originalRight + 'px' });
    				} else {
    					appendToElement.css({ paddingRight: '' });
    				}
    				scrollbarPadding = null;
    			}
    			toggleTopWindowClass(true);
    		}, modalWindow.closedDeferred);
    		checkRemoveBackdrop();

    		//move focus to specified element if available, or else to body
    		if (elementToReceiveFocus && elementToReceiveFocus.focus) {
    			elementToReceiveFocus.focus();
    		} else if (appendToElement.focus) {
    			appendToElement.focus();
    		}
    	}

    	// Add or remove "windowTopClass" from the top window in the stack
    	function toggleTopWindowClass(toggleSwitch) {
    		var modalWindow;

    		if (openedWindows.length() > 0) {
    			modalWindow = openedWindows.top().value;
    			modalWindow.modalDomEl.toggleClass(modalWindow.windowTopClass || '', toggleSwitch);
    		}
    	}

    	function checkRemoveBackdrop() {
    		//remove backdrop if no longer needed
    		if (backdropDomEl && backdropIndex() === -1) {
    			var backdropScopeRef = backdropScope;
    			removeAfterAnimate(backdropDomEl, backdropScope, function () {
    				backdropScopeRef = null;
    			});
    			backdropDomEl = undefined;
    			backdropScope = undefined;
    		}
    	}

    	function removeAfterAnimate(domEl, scope, done, closedDeferred) {
    		var asyncDeferred;
    		var asyncPromise = null;
    		var setIsAsync = function () {
    			if (!asyncDeferred) {
    				asyncDeferred = $q.defer();
    				asyncPromise = asyncDeferred.promise;
    			}

    			return function asyncDone() {
    				asyncDeferred.resolve();
    			};
    		};
    		scope.$broadcast($modalStack.NOW_CLOSING_EVENT, setIsAsync);

    		// Note that it's intentional that asyncPromise might be null.
    		// That's when setIsAsync has not been called during the
    		// NOW_CLOSING_EVENT broadcast.
    		return $q.when(asyncPromise).then(afterAnimating);

    		function afterAnimating() {
    			if (afterAnimating.done) {
    				return;
    			}
    			afterAnimating.done = true;

    			$animate.leave(domEl).then(function () {
    				if (done) {
    					done();
    				}

    				domEl.remove();
    				if (closedDeferred) {
    					closedDeferred.resolve();
    				}
    			});

    			scope.$destroy();
    		}
    	}

    	$document.on('keydown', keydownListener);

    	$rootScope.$on('$destroy', function () {
    		$document.off('keydown', keydownListener);
    	});

    	function keydownListener(evt) {
    		if (evt.isDefaultPrevented()) {
    			return evt;
    		}

    		var modal = openedWindows.top();
    		if (modal) {
    			switch (evt.which) {
    				case 27: {
    					if (modal.value.keyboard) {
    						evt.preventDefault();
    						$rootScope.$apply(function () {
    							$modalStack.dismiss(modal.key, 'escape key press');
    						});
    					}
    					break;
    				}
    				case 9: {
    					var list = $modalStack.loadFocusElementList(modal);
    					var focusChanged = false;
    					if (evt.shiftKey) {
    						if ($modalStack.isFocusInFirstItem(evt, list) || $modalStack.isModalFocused(evt, modal)) {
    							focusChanged = $modalStack.focusLastFocusableElement(list);
    						}
    					} else {
    						if ($modalStack.isFocusInLastItem(evt, list)) {
    							focusChanged = $modalStack.focusFirstFocusableElement(list);
    						}
    					}

    					if (focusChanged) {
    						evt.preventDefault();
    						evt.stopPropagation();
    					}

    					break;
    				}
    			}
    		}
    	}

    	$modalStack.open = function (modalInstance, modal) {
    		var modalOpener = $document[0].activeElement,
			  modalBodyClass = modal.openedClass || OPENED_MODAL_CLASS;

    		toggleTopWindowClass(false);

    		// Store the current top first, to determine what index we ought to use
    		// for the current top modal
    		previousTopOpenedModal = openedWindows.top();

    		openedWindows.add(modalInstance, {
    			deferred: modal.deferred,
    			renderDeferred: modal.renderDeferred,
    			closedDeferred: modal.closedDeferred,
    			modalScope: modal.scope,
    			backdrop: modal.backdrop,
    			keyboard: modal.keyboard,
    			openedClass: modal.openedClass,
    			windowTopClass: modal.windowTopClass,
    			animation: modal.animation,
    			appendTo: modal.appendTo
    		});

    		openedClasses.put(modalBodyClass, modalInstance);

    		var appendToElement = modal.appendTo,
				currBackdropIndex = backdropIndex();

    		if (!appendToElement.length) {
    			throw new Error('appendTo element not found. Make sure that the element passed is in DOM.');
    		}

    		if (currBackdropIndex >= 0 && !backdropDomEl) {
    			backdropScope = $rootScope.$new(true);
    			backdropScope.modalOptions = modal;
    			backdropScope.index = currBackdropIndex;
    			backdropDomEl = angular.element('<div uib-modal-backdrop="modal-backdrop"></div>');
    			backdropDomEl.attr({
    				'class': 'modal-backdrop',
    				'ng-style': '{\'z-index\': 1040 + (index && 1 || 0) + index*10}',
    				'uib-modal-animation-class': 'fade',
    				'modal-in-class': 'in'
    			});
    			if (modal.backdropClass) {
    				backdropDomEl.addClass(modal.backdropClass);
    			}

    			if (modal.animation) {
    				backdropDomEl.attr('modal-animation', 'true');
    			}
    			$compile(backdropDomEl)(backdropScope);
    			$animate.enter(backdropDomEl, appendToElement);
    			if ($uibPosition.isScrollable(appendToElement)) {
    				scrollbarPadding = $uibPosition.scrollbarPadding(appendToElement);
    				if (scrollbarPadding.heightOverflow && scrollbarPadding.scrollbarWidth) {
    					appendToElement.css({ paddingRight: scrollbarPadding.right + 'px' });
    				}
    			}
    		}

    		var content;
    		if (modal.component) {
    			content = document.createElement(snake_case(modal.component.name));
    			content = angular.element(content);
    			content.attr({
    				resolve: '$resolve',
    				'modal-instance': '$uibModalInstance',
    				close: '$close($value)',
    				dismiss: '$dismiss($value)'
                });
                // Commented the below line in order to avoid the uibModal component feature initiates twice
    			//content = $compile(content)(modal.scope);
    		} else {
    			content = modal.content;
    		}

    		// Set the top modal index based on the index of the previous top modal
    		topModalIndex = previousTopOpenedModal ? parseInt(previousTopOpenedModal.value.modalDomEl.attr('index'), 10) + 1 : 0;
    		var angularDomEl = angular.element('<div uib-modal-window="modal-window"></div>');
    		angularDomEl.attr({
    			'class': 'modal',
    			'template-url': modal.windowTemplateUrl,
    			'window-top-class': modal.windowTopClass,
    			'role': 'dialog',
    			'aria-labelledby': modal.ariaLabelledBy,
    			'aria-describedby': modal.ariaDescribedBy,
    			'size': modal.size,
    			'index': topModalIndex,
    			'animate': 'animate',
    			'ng-style': '{\'z-index\': 1050 + $$topModalIndex*10, display: \'block\'}',
    			'tabindex': -1,
    			'uib-modal-animation-class': 'fade',
    			'modal-in-class': 'in'
    		}).append(content);
    		if (modal.windowClass) {
    			angularDomEl.addClass(modal.windowClass);
    		}

    		if (modal.animation) {
    			angularDomEl.attr('modal-animation', 'true');
    		}

    		appendToElement.addClass(modalBodyClass);
    		if (modal.scope) {
    			// we need to explicitly add the modal index to the modal scope
    			// because it is needed by ngStyle to compute the zIndex property.
    			modal.scope.$$topModalIndex = topModalIndex;
    		}
    		$animate.enter($compile(angularDomEl)(modal.scope), appendToElement);

    		openedWindows.top().value.modalDomEl = angularDomEl;
    		openedWindows.top().value.modalOpener = modalOpener;
    	};

    	function broadcastClosing(modalWindow, resultOrReason, closing) {
    		return !modalWindow.value.modalScope.$broadcast('modal.closing', resultOrReason, closing).defaultPrevented;
    	}

    	$modalStack.close = function (modalInstance, result) {
    		var modalWindow = openedWindows.get(modalInstance);
    		if (modalWindow && broadcastClosing(modalWindow, result, true)) {
    			modalWindow.value.modalScope.$$uibDestructionScheduled = true;
    			modalWindow.value.deferred.resolve(result);
    			removeModalWindow(modalInstance, modalWindow.value.modalOpener);
    			return true;
    		}
    		return !modalWindow;
    	};

    	$modalStack.dismiss = function (modalInstance, reason) {
    		var modalWindow = openedWindows.get(modalInstance);
    		if (modalWindow && broadcastClosing(modalWindow, reason, false)) {
    			modalWindow.value.modalScope.$$uibDestructionScheduled = true;
    			modalWindow.value.deferred.reject(reason);
    			removeModalWindow(modalInstance, modalWindow.value.modalOpener);
    			return true;
    		}
    		return !modalWindow;
    	};

    	$modalStack.dismissAll = function (reason) {
    		var topModal = this.getTop();
    		while (topModal && this.dismiss(topModal.key, reason)) {
    			topModal = this.getTop();
    		}
    	};

    	$modalStack.getTop = function () {
    		return openedWindows.top();
    	};

    	$modalStack.modalRendered = function (modalInstance) {
    		var modalWindow = openedWindows.get(modalInstance);
    		if (modalWindow) {
    			modalWindow.value.renderDeferred.resolve();
    		}
    	};

    	$modalStack.focusFirstFocusableElement = function (list) {
    		if (list.length > 0) {
    			list[0].focus();
    			return true;
    		}
    		return false;
    	};

    	$modalStack.focusLastFocusableElement = function (list) {
    		if (list.length > 0) {
    			list[list.length - 1].focus();
    			return true;
    		}
    		return false;
    	};

    	$modalStack.isModalFocused = function (evt, modalWindow) {
    		if (evt && modalWindow) {
    			var modalDomEl = modalWindow.value.modalDomEl;
    			if (modalDomEl && modalDomEl.length) {
    				return (evt.target || evt.srcElement) === modalDomEl[0];
    			}
    		}
    		return false;
    	};

    	$modalStack.isFocusInFirstItem = function (evt, list) {
    		if (list.length > 0) {
    			return (evt.target || evt.srcElement) === list[0];
    		}
    		return false;
    	};

    	$modalStack.isFocusInLastItem = function (evt, list) {
    		if (list.length > 0) {
    			return (evt.target || evt.srcElement) === list[list.length - 1];
    		}
    		return false;
    	};

    	$modalStack.loadFocusElementList = function (modalWindow) {
    		if (modalWindow) {
    			var modalDomE1 = modalWindow.value.modalDomEl;
    			if (modalDomE1 && modalDomE1.length) {
    				var elements = modalDomE1[0].querySelectorAll(tabbableSelector);
    				return elements ?
					  Array.prototype.filter.call(elements, function (element) {
					  	return isVisible(element);
					  }) : elements;
    			}
    		}
    	};

    	return $modalStack;
    }])

  .provider('$uibModal', function () {
  	var $modalProvider = {
  		options: {
  			animation: true,
  			backdrop: true, //can also be false or 'static'
  			keyboard: true
  		},
  		$get: ['$rootScope', '$q', '$document', '$templateRequest', '$controller', '$uibResolve', '$uibModalStack',
		  function ($rootScope, $q, $document, $templateRequest, $controller, $uibResolve, $modalStack) {
		  	var $modal = {};

		  	function getTemplatePromise(options) {
		  		return options.template ? $q.when(options.template) :
				  $templateRequest(angular.isFunction(options.templateUrl) ?
					options.templateUrl() : options.templateUrl);
		  	}

		  	var promiseChain = null;
		  	$modal.getPromiseChain = function () {
		  		return promiseChain;
		  	};

		  	$modal.open = function (modalOptions) {
		  		var modalResultDeferred = $q.defer();
		  		var modalOpenedDeferred = $q.defer();
		  		var modalClosedDeferred = $q.defer();
		  		var modalRenderDeferred = $q.defer();

		  		//prepare an instance of a modal to be injected into controllers and returned to a caller
		  		var modalInstance = {
		  			result: modalResultDeferred.promise,
		  			opened: modalOpenedDeferred.promise,
		  			closed: modalClosedDeferred.promise,
		  			rendered: modalRenderDeferred.promise,
		  			close: function (result) {
		  				return $modalStack.close(modalInstance, result);
		  			},
		  			dismiss: function (reason) {
		  				return $modalStack.dismiss(modalInstance, reason);
		  			}
		  		};

		  		//merge and clean up options
		  		modalOptions = angular.extend({}, $modalProvider.options, modalOptions);
		  		modalOptions.resolve = modalOptions.resolve || {};
		  		modalOptions.appendTo = modalOptions.appendTo || $document.find('body').eq(0);

		  		//verify options
		  		if (!modalOptions.component && !modalOptions.template && !modalOptions.templateUrl) {
		  			throw new Error('One of component or template or templateUrl options is required.');
		  		}

		  		var templateAndResolvePromise;
		  		if (modalOptions.component) {
		  			templateAndResolvePromise = $q.when($uibResolve.resolve(modalOptions.resolve, {}, null, null));
		  		} else {
		  			templateAndResolvePromise =
					  $q.all([getTemplatePromise(modalOptions), $uibResolve.resolve(modalOptions.resolve, {}, null, null)]);
		  		}

		  		function resolveWithTemplate() {
		  			return templateAndResolvePromise;
		  		}

		  		// Wait for the resolution of the existing promise chain.
		  		// Then switch to our own combined promise dependency (regardless of how the previous modal fared).
		  		// Then add to $modalStack and resolve opened.
		  		// Finally clean up the chain variable if no subsequent modal has overwritten it.
		  		var samePromise;
		  		samePromise = promiseChain = $q.all([promiseChain])
				  .then(resolveWithTemplate, resolveWithTemplate)
				  .then(function resolveSuccess(tplAndVars) {
				  	var providedScope = modalOptions.scope || $rootScope;

				  	var modalScope = providedScope.$new();
				  	modalScope.$close = modalInstance.close;
				  	modalScope.$dismiss = modalInstance.dismiss;

				  	modalScope.$on('$destroy', function () {
				  		if (!modalScope.$$uibDestructionScheduled) {
				  			modalScope.$dismiss('$uibUnscheduledDestruction');
				  		}
				  	});

				  	var modal = {
				  		scope: modalScope,
				  		deferred: modalResultDeferred,
				  		renderDeferred: modalRenderDeferred,
				  		closedDeferred: modalClosedDeferred,
				  		animation: modalOptions.animation,
				  		backdrop: modalOptions.backdrop,
				  		keyboard: modalOptions.keyboard,
				  		backdropClass: modalOptions.backdropClass,
				  		windowTopClass: modalOptions.windowTopClass,
				  		windowClass: modalOptions.windowClass,
				  		windowTemplateUrl: modalOptions.windowTemplateUrl,
				  		ariaLabelledBy: modalOptions.ariaLabelledBy,
				  		ariaDescribedBy: modalOptions.ariaDescribedBy,
				  		size: modalOptions.size,
				  		openedClass: modalOptions.openedClass,
				  		appendTo: modalOptions.appendTo
				  	};

				  	var component = {};
				  	var ctrlInstance, ctrlInstantiate, ctrlLocals = {};

				  	if (modalOptions.component) {
				  		constructLocals(component, false, true, false);
				  		component.name = modalOptions.component;
				  		modal.component = component;
				  	} else if (modalOptions.controller) {
				  		constructLocals(ctrlLocals, true, false, true);

				  		// the third param will make the controller instantiate later,private api
				  		// @see https://github.com/angular/angular.js/blob/master/src/ng/controller.js#L126
				  		ctrlInstantiate = $controller(modalOptions.controller, ctrlLocals, true, modalOptions.controllerAs);
				  		if (modalOptions.controllerAs && modalOptions.bindToController) {
				  			ctrlInstance = ctrlInstantiate.instance;
				  			ctrlInstance.$close = modalScope.$close;
				  			ctrlInstance.$dismiss = modalScope.$dismiss;
				  			angular.extend(ctrlInstance, {
				  				$resolve: ctrlLocals.$scope.$resolve
				  			}, providedScope);
				  		}

				  		ctrlInstance = ctrlInstantiate();

				  		if (angular.isFunction(ctrlInstance.$onInit)) {
				  			ctrlInstance.$onInit();
				  		}
				  	}

				  	if (!modalOptions.component) {
				  		modal.content = tplAndVars[0];
				  	}

				  	$modalStack.open(modalInstance, modal);
				  	modalOpenedDeferred.resolve(true);

				  	function constructLocals(obj, template, instanceOnScope, injectable) {
				  		obj.$scope = modalScope;
				  		obj.$scope.$resolve = {};
				  		if (instanceOnScope) {
				  			obj.$scope.$uibModalInstance = modalInstance;
				  		} else {
				  			obj.$uibModalInstance = modalInstance;
				  		}

				  		var resolves = template ? tplAndVars[1] : tplAndVars;
				  		angular.forEach(resolves, function (value, key) {
				  			if (injectable) {
				  				obj[key] = value;
				  			}

				  			obj.$scope.$resolve[key] = value;
				  		});
				  	}
				  }, function resolveError(reason) {
				  	modalOpenedDeferred.reject(reason);
				  	modalResultDeferred.reject(reason);
				  })['finally'](function () {
				  	if (promiseChain === samePromise) {
				  		promiseChain = null;
				  	}
				  });

		  		return modalInstance;
		  	};

		  	return $modal;
		  }
  		]
  	};

  	return $modalProvider;
  });

angular.module('ui.bootstrap.paging', [])
/**
 * Helper internal service for generating common controller code between the
 * pager and pagination components
 */
.factory('uibPaging', ['$parse', function ($parse) {
	return {
		create: function (ctrl, $scope, $attrs) {
			ctrl.setNumPages = $attrs.numPages ? $parse($attrs.numPages).assign : angular.noop;
			ctrl.ngModelCtrl = { $setViewValue: angular.noop }; // nullModelCtrl
			ctrl._watchers = [];

			ctrl.init = function (ngModelCtrl, config) {
				ctrl.ngModelCtrl = ngModelCtrl;
				ctrl.config = config;

				ngModelCtrl.$render = function () {
					ctrl.render();
				};

				if ($attrs.itemsPerPage) {
					ctrl._watchers.push($scope.$parent.$watch($attrs.itemsPerPage, function (value) {
						ctrl.itemsPerPage = parseInt(value, 10);
						$scope.totalPages = ctrl.calculateTotalPages();
						ctrl.updatePage();
					}));
				} else {
					ctrl.itemsPerPage = config.itemsPerPage;
				}

				$scope.$watch('totalItems', function (newTotal, oldTotal) {
					if (angular.isDefined(newTotal) || newTotal !== oldTotal) {
						$scope.totalPages = ctrl.calculateTotalPages();
						ctrl.updatePage();
					}
				});
			};

			ctrl.calculateTotalPages = function () {
				var totalPages = ctrl.itemsPerPage < 1 ? 1 : Math.ceil($scope.totalItems / ctrl.itemsPerPage);
				return Math.max(totalPages || 0, 1);
			};

			ctrl.render = function () {
				$scope.page = parseInt(ctrl.ngModelCtrl.$viewValue, 10) || 1;
			};

			$scope.selectPage = function (page, evt) {
				if (evt) {
					evt.preventDefault();
				}

				var clickAllowed = !$scope.ngDisabled || !evt;
				if (clickAllowed && $scope.page !== page && page > 0 && page <= $scope.totalPages) {
					if (evt && evt.target) {
						evt.target.blur();
					}
					ctrl.ngModelCtrl.$setViewValue(page);
					ctrl.ngModelCtrl.$render();
				}
			};

			$scope.getText = function (key) {
				return $scope[key + 'Text'] || ctrl.config[key + 'Text'];
			};

			$scope.noPrevious = function () {
				return $scope.page === 1;
			};

			$scope.noNext = function () {
				return $scope.page === $scope.totalPages;
			};

			ctrl.updatePage = function () {
				ctrl.setNumPages($scope.$parent, $scope.totalPages); // Readonly variable

				if ($scope.page > $scope.totalPages) {
					$scope.selectPage($scope.totalPages);
				} else {
					ctrl.ngModelCtrl.$render();
				}
			};

			$scope.$on('$destroy', function () {
				while (ctrl._watchers.length) {
					ctrl._watchers.shift()();
				}
			});
		}
	};
}]);

angular.module('ui.bootstrap.pager', ['ui.bootstrap.paging', 'ui.bootstrap.tabindex'])

.controller('UibPagerController', ['$scope', '$attrs', 'uibPaging', 'uibPagerConfig', function ($scope, $attrs, uibPaging, uibPagerConfig) {
	$scope.align = angular.isDefined($attrs.align) ? $scope.$parent.$eval($attrs.align) : uibPagerConfig.align;

	uibPaging.create(this, $scope, $attrs);
}])

.constant('uibPagerConfig', {
	itemsPerPage: 10,
	previousText: '« Previous',
	nextText: 'Next »',
	align: true
})

.directive('uibPager', ['uibPagerConfig', function (uibPagerConfig) {
	return {
		scope: {
			totalItems: '=',
			previousText: '@',
			nextText: '@',
			ngDisabled: '='
		},
		require: ['uibPager', '?ngModel'],
		restrict: 'A',
		controller: 'UibPagerController',
		controllerAs: 'pager',
		templateUrl: function (element, attrs) {
			return attrs.templateUrl || 'uib/template/pager/pager.html';
		},
		link: function (scope, element, attrs, ctrls) {
			element.addClass('pager');
			var paginationCtrl = ctrls[0], ngModelCtrl = ctrls[1];

			if (!ngModelCtrl) {
				return; // do nothing if no ng-model
			}

			paginationCtrl.init(ngModelCtrl, uibPagerConfig);
		}
	};
}]);

angular.module('ui.bootstrap.pagination', ['ui.bootstrap.paging', 'ui.bootstrap.tabindex'])
.controller('UibPaginationController', ['$scope', '$attrs', '$parse', 'uibPaging', 'uibPaginationConfig', function ($scope, $attrs, $parse, uibPaging, uibPaginationConfig) {
	var ctrl = this;
	// Setup configuration parameters
	var maxSize = angular.isDefined($attrs.maxSize) ? $scope.$parent.$eval($attrs.maxSize) : uibPaginationConfig.maxSize,
	  rotate = angular.isDefined($attrs.rotate) ? $scope.$parent.$eval($attrs.rotate) : uibPaginationConfig.rotate,
	  forceEllipses = angular.isDefined($attrs.forceEllipses) ? $scope.$parent.$eval($attrs.forceEllipses) : uibPaginationConfig.forceEllipses,
	  boundaryLinkNumbers = angular.isDefined($attrs.boundaryLinkNumbers) ? $scope.$parent.$eval($attrs.boundaryLinkNumbers) : uibPaginationConfig.boundaryLinkNumbers,
	  pageLabel = angular.isDefined($attrs.pageLabel) ? function (idx) { return $scope.$parent.$eval($attrs.pageLabel, { $page: idx }); } : angular.identity;
	$scope.boundaryLinks = angular.isDefined($attrs.boundaryLinks) ? $scope.$parent.$eval($attrs.boundaryLinks) : uibPaginationConfig.boundaryLinks;
	$scope.directionLinks = angular.isDefined($attrs.directionLinks) ? $scope.$parent.$eval($attrs.directionLinks) : uibPaginationConfig.directionLinks;

	uibPaging.create(this, $scope, $attrs);

	if ($attrs.maxSize) {
		ctrl._watchers.push($scope.$parent.$watch($parse($attrs.maxSize), function (value) {
			maxSize = parseInt(value, 10);
			ctrl.render();
		}));
	}

	// Create page object used in template
	function makePage(number, text, isActive) {
		return {
			number: number,
			text: text,
			active: isActive
		};
	}

	function getPages(currentPage, totalPages) {
		var pages = [];

		// Default page limits
		var startPage = 1, endPage = totalPages;
		var isMaxSized = angular.isDefined(maxSize) && maxSize < totalPages;

		// recompute if maxSize
		if (isMaxSized) {
			if (rotate) {
				// Current page is displayed in the middle of the visible ones
				startPage = Math.max(currentPage - Math.floor(maxSize / 2), 1);
				endPage = startPage + maxSize - 1;

				// Adjust if limit is exceeded
				if (endPage > totalPages) {
					endPage = totalPages;
					startPage = endPage - maxSize + 1;
				}
			} else {
				// Visible pages are paginated with maxSize
				startPage = (Math.ceil(currentPage / maxSize) - 1) * maxSize + 1;

				// Adjust last page if limit is exceeded
				endPage = Math.min(startPage + maxSize - 1, totalPages);
			}
		}

		// Add page number links
		for (var number = startPage; number <= endPage; number++) {
			var page = makePage(number, pageLabel(number), number === currentPage);
			pages.push(page);
		}

		// Add links to move between page sets
		if (isMaxSized && maxSize > 0 && (!rotate || forceEllipses || boundaryLinkNumbers)) {
			if (startPage > 1) {
				if (!boundaryLinkNumbers || startPage > 3) { //need ellipsis for all options unless range is too close to beginning
					var previousPageSet = makePage(startPage - 1, '...', false);
					pages.unshift(previousPageSet);
				}
				if (boundaryLinkNumbers) {
					if (startPage === 3) { //need to replace ellipsis when the buttons would be sequential
						var secondPageLink = makePage(2, '2', false);
						pages.unshift(secondPageLink);
					}
					//add the first page
					var firstPageLink = makePage(1, '1', false);
					pages.unshift(firstPageLink);
				}
			}

			if (endPage < totalPages) {
				if (!boundaryLinkNumbers || endPage < totalPages - 2) { //need ellipsis for all options unless range is too close to end
					var nextPageSet = makePage(endPage + 1, '...', false);
					pages.push(nextPageSet);
				}
				if (boundaryLinkNumbers) {
					if (endPage === totalPages - 2) { //need to replace ellipsis when the buttons would be sequential
						var secondToLastPageLink = makePage(totalPages - 1, totalPages - 1, false);
						pages.push(secondToLastPageLink);
					}
					//add the last page
					var lastPageLink = makePage(totalPages, totalPages, false);
					pages.push(lastPageLink);
				}
			}
		}
		return pages;
	}

	var originalRender = this.render;
	this.render = function () {
		originalRender();
		if ($scope.page > 0 && $scope.page <= $scope.totalPages) {
			$scope.pages = getPages($scope.page, $scope.totalPages);
		}
	};
}])

.constant('uibPaginationConfig', {
	itemsPerPage: 10,
	boundaryLinks: false,
	boundaryLinkNumbers: false,
	directionLinks: true,
	firstText: 'First',
	previousText: 'Previous',
	nextText: 'Next',
	lastText: 'Last',
	rotate: true,
	forceEllipses: false
})

.directive('uibPagination', ['$parse', 'uibPaginationConfig', function ($parse, uibPaginationConfig) {
	return {
		scope: {
			totalItems: '=',
			firstText: '@',
			previousText: '@',
			nextText: '@',
			lastText: '@',
			ngDisabled: '='
		},
		require: ['uibPagination', '?ngModel'],
		restrict: 'A',
		controller: 'UibPaginationController',
		controllerAs: 'pagination',
		templateUrl: function (element, attrs) {
			return attrs.templateUrl || 'uib/template/pagination/pagination.html';
		},
		link: function (scope, element, attrs, ctrls) {
			element.addClass('pagination');
			var paginationCtrl = ctrls[0], ngModelCtrl = ctrls[1];

			if (!ngModelCtrl) {
				return; // do nothing if no ng-model
			}

			paginationCtrl.init(ngModelCtrl, uibPaginationConfig);
		}
	};
}]);

/**
 * The following features are still outstanding: animation as a
 * function, placement as a function, inside, support for more triggers than
 * just mouse enter/leave, html tooltips, and selector delegation.
 */
angular.module('ui.bootstrap.tooltip', ['ui.bootstrap.position', 'ui.bootstrap.stackedMap'])

/**
 * The $tooltip service creates tooltip- and popover-like directives as well as
 * houses global options for them.
 */
.provider('$uibTooltip', function () {
	// The default options tooltip and popover.
	var defaultOptions = {
		placement: 'top',
		placementClassPrefix: '',
		animation: true,
		popupDelay: 0,
		popupCloseDelay: 0,
		useContentExp: false
	};

	// Default hide triggers for each show trigger
	var triggerMap = {
		'mouseenter': 'mouseleave',
		'click': 'click',
		'outsideClick': 'outsideClick',
		'focus': 'blur',
		'none': ''
	};

	// The options specified to the provider globally.
	var globalOptions = {};

	/**
	 * `options({})` allows global configuration of all tooltips in the
	 * application.
	 *
	 *   var app = angular.module( 'App', ['ui.bootstrap.tooltip'], function( $tooltipProvider ) {
	 *     // place tooltips left instead of top by default
	 *     $tooltipProvider.options( { placement: 'left' } );
	 *   });
	 */
	this.options = function (value) {
		angular.extend(globalOptions, value);
	};

	/**
	 * This allows you to extend the set of trigger mappings available. E.g.:
	 *
	 *   $tooltipProvider.setTriggers( { 'openTrigger': 'closeTrigger' } );
	 */
	this.setTriggers = function setTriggers(triggers) {
		angular.extend(triggerMap, triggers);
	};

	/**
	 * This is a helper function for translating camel-case to snake_case.
	 */
	function snake_case(name) {
		var regexp = /[A-Z]/g;
		var separator = '-';
		return name.replace(regexp, function (letter, pos) {
			return (pos ? separator : '') + letter.toLowerCase();
		});
	}

	/**
	 * Returns the actual instance of the $tooltip service.
	 * TODO support multiple triggers
	 */
	this.$get = ['$window', '$compile', '$timeout', '$document', '$uibPosition', '$interpolate', '$rootScope', '$parse', '$$stackedMap', function ($window, $compile, $timeout, $document, $position, $interpolate, $rootScope, $parse, $$stackedMap) {
		var openedTooltips = $$stackedMap.createNew();
		$document.on('keyup', keypressListener);

		$rootScope.$on('$destroy', function () {
			$document.off('keyup', keypressListener);
		});

		function keypressListener(e) {
			if (e.which === 27) {
				var last = openedTooltips.top();
				if (last) {
					last.value.close();
					last = null;
				}
			}
		}

		return function $tooltip(ttType, prefix, defaultTriggerShow, options) {
			options = angular.extend({}, defaultOptions, globalOptions, options);

			/**
			 * Returns an object of show and hide triggers.
			 *
			 * If a trigger is supplied,
			 * it is used to show the tooltip; otherwise, it will use the `trigger`
			 * option passed to the `$tooltipProvider.options` method; else it will
			 * default to the trigger supplied to this directive factory.
			 *
			 * The hide trigger is based on the show trigger. If the `trigger` option
			 * was passed to the `$tooltipProvider.options` method, it will use the
			 * mapped trigger from `triggerMap` or the passed trigger if the map is
			 * undefined; otherwise, it uses the `triggerMap` value of the show
			 * trigger; else it will just use the show trigger.
			 */
			function getTriggers(trigger) {
				var show = (trigger || options.trigger || defaultTriggerShow).split(' ');
				var hide = show.map(function (trigger) {
					return triggerMap[trigger] || trigger;
				});
				return {
					show: show,
					hide: hide
				};
			}

			var directiveName = snake_case(ttType);

			var startSym = $interpolate.startSymbol();
			var endSym = $interpolate.endSymbol();
			var template =
			  '<div ' + directiveName + '-popup ' +
				'uib-title="' + startSym + 'title' + endSym + '" ' +
				(options.useContentExp ?
				  'content-exp="contentExp()" ' :
				  'content="' + startSym + 'content' + endSym + '" ') +
				'origin-scope="origScope" ' +
				'class="uib-position-measure ' + prefix + '" ' +
				'tooltip-animation-class="fade"' +
				'uib-tooltip-classes ' +
				'ng-class="{ in: isOpen }" ' +
				'>' +
			  '</div>';

			return {
				compile: function (tElem, tAttrs) {
					var tooltipLinker = $compile(template);

					return function link(scope, element, attrs, tooltipCtrl) {
						var tooltip;
						var tooltipLinkedScope;
						var transitionTimeout;
						var showTimeout;
						var hideTimeout;
						var positionTimeout;
						var appendToBody = angular.isDefined(options.appendToBody) ? options.appendToBody : false;
						var triggers = getTriggers(undefined);
						var hasEnableExp = angular.isDefined(attrs[prefix + 'Enable']);
						var ttScope = scope.$new(true);
						var repositionScheduled = false;
						var isOpenParse = angular.isDefined(attrs[prefix + 'IsOpen']) ? $parse(attrs[prefix + 'IsOpen']) : false;
						var contentParse = options.useContentExp ? $parse(attrs[ttType]) : false;
						var observers = [];
						var lastPlacement;

						var positionTooltip = function () {
							// check if tooltip exists and is not empty
							if (!tooltip || !tooltip.html()) { return; }

							if (!positionTimeout) {
								positionTimeout = $timeout(function () {
									var ttPosition = $position.positionElements(element, tooltip, ttScope.placement, appendToBody);
									var initialHeight = angular.isDefined(tooltip.offsetHeight) ? tooltip.offsetHeight : tooltip.prop('offsetHeight');
									var elementPos = appendToBody ? $position.offset(element) : $position.position(element);
									tooltip.css({ top: ttPosition.top + 'px', left: ttPosition.left + 'px' });
									var placementClasses = ttPosition.placement.split('-');

									if (!tooltip.hasClass(placementClasses[0])) {
										tooltip.removeClass(lastPlacement.split('-')[0]);
										tooltip.addClass(placementClasses[0]);
									}

									if (!tooltip.hasClass(options.placementClassPrefix + ttPosition.placement)) {
										tooltip.removeClass(options.placementClassPrefix + lastPlacement);
										tooltip.addClass(options.placementClassPrefix + ttPosition.placement);
									}

									$timeout(function () {
										var currentHeight = angular.isDefined(tooltip.offsetHeight) ? tooltip.offsetHeight : tooltip.prop('offsetHeight');
										var adjustment = $position.adjustTop(placementClasses, elementPos, initialHeight, currentHeight);
										if (adjustment) {
											tooltip.css(adjustment);
										}
									}, 0, false);

									// first time through tt element will have the
									// uib-position-measure class or if the placement
									// has changed we need to position the arrow.
									if (tooltip.hasClass('uib-position-measure')) {
										$position.positionArrow(tooltip, ttPosition.placement);
										tooltip.removeClass('uib-position-measure');
									} else if (lastPlacement !== ttPosition.placement) {
										$position.positionArrow(tooltip, ttPosition.placement);
									}
									lastPlacement = ttPosition.placement;

									positionTimeout = null;
								}, 0, false);
							}
						};

						// Set up the correct scope to allow transclusion later
						ttScope.origScope = scope;

						// By default, the tooltip is not open.
						// TODO add ability to start tooltip opened
						ttScope.isOpen = false;

						function toggleTooltipBind() {
							if (!ttScope.isOpen) {
								showTooltipBind();
							} else {
								hideTooltipBind();
							}
						}

						// Show the tooltip with delay if specified, otherwise show it immediately
						function showTooltipBind() {
							if (hasEnableExp && !scope.$eval(attrs[prefix + 'Enable'])) {
								return;
							}

							cancelHide();
							prepareTooltip();

							if (ttScope.popupDelay) {
								// Do nothing if the tooltip was already scheduled to pop-up.
								// This happens if show is triggered multiple times before any hide is triggered.
								if (!showTimeout) {
									showTimeout = $timeout(show, ttScope.popupDelay, false);
								}
							} else {
								show();
							}
						}

						function hideTooltipBind() {
							cancelShow();

							if (ttScope.popupCloseDelay) {
								if (!hideTimeout) {
									hideTimeout = $timeout(hide, ttScope.popupCloseDelay, false);
								}
							} else {
								hide();
							}
						}

						// Show the tooltip popup element.
						function show() {
							cancelShow();
							cancelHide();

							// Don't show empty tooltips.
							if (!ttScope.content) {
								return angular.noop;
							}

							createTooltip();

							// And show the tooltip.
							ttScope.$evalAsync(function () {
								ttScope.isOpen = true;
								assignIsOpen(true);
								positionTooltip();
							});
						}

						function cancelShow() {
							if (showTimeout) {
								$timeout.cancel(showTimeout);
								showTimeout = null;
							}

							if (positionTimeout) {
								$timeout.cancel(positionTimeout);
								positionTimeout = null;
							}
						}

						// Hide the tooltip popup element.
						function hide() {
							if (!ttScope) {
								return;
							}

							// First things first: we don't show it anymore.
							ttScope.$evalAsync(function () {
								if (ttScope) {
									ttScope.isOpen = false;
									assignIsOpen(false);
									// And now we remove it from the DOM. However, if we have animation, we
									// need to wait for it to expire beforehand.
									// FIXME: this is a placeholder for a port of the transitions library.
									// The fade transition in TWBS is 150ms.
									if (ttScope.animation) {
										if (!transitionTimeout) {
											transitionTimeout = $timeout(removeTooltip, 150, false);
										}
									} else {
										removeTooltip();
									}
								}
							});
						}

						function cancelHide() {
							if (hideTimeout) {
								$timeout.cancel(hideTimeout);
								hideTimeout = null;
							}

							if (transitionTimeout) {
								$timeout.cancel(transitionTimeout);
								transitionTimeout = null;
							}
						}

						function createTooltip() {
							// There can only be one tooltip element per directive shown at once.
							if (tooltip) {
								return;
							}

							tooltipLinkedScope = ttScope.$new();
							tooltip = tooltipLinker(tooltipLinkedScope, function (tooltip) {
								if (appendToBody) {
									$document.find('body').append(tooltip);
								} else {
									element.after(tooltip);
								}
							});

							openedTooltips.add(ttScope, {
								close: hide
							});

							prepObservers();
						}

						function removeTooltip() {
							cancelShow();
							cancelHide();
							unregisterObservers();

							if (tooltip) {
								tooltip.remove();
								tooltip = null;
							}

							openedTooltips.remove(ttScope);

							if (tooltipLinkedScope) {
								tooltipLinkedScope.$destroy();
								tooltipLinkedScope = null;
							}
						}

						/**
						 * Set the initial scope values. Once
						 * the tooltip is created, the observers
						 * will be added to keep things in sync.
						 */
						function prepareTooltip() {
							ttScope.title = attrs[prefix + 'Title'];
							if (contentParse) {
								ttScope.content = contentParse(scope);
							} else {
								ttScope.content = attrs[ttType];
							}

							ttScope.popupClass = attrs[prefix + 'Class'];
							ttScope.placement = angular.isDefined(attrs[prefix + 'Placement']) ? attrs[prefix + 'Placement'] : options.placement;
							var placement = $position.parsePlacement(ttScope.placement);
							lastPlacement = placement[1] ? placement[0] + '-' + placement[1] : placement[0];

							var delay = parseInt(attrs[prefix + 'PopupDelay'], 10);
							var closeDelay = parseInt(attrs[prefix + 'PopupCloseDelay'], 10);
							ttScope.popupDelay = !isNaN(delay) ? delay : options.popupDelay;
							ttScope.popupCloseDelay = !isNaN(closeDelay) ? closeDelay : options.popupCloseDelay;
						}

						function assignIsOpen(isOpen) {
							if (isOpenParse && angular.isFunction(isOpenParse.assign)) {
								isOpenParse.assign(scope, isOpen);
							}
						}

						ttScope.contentExp = function () {
							return ttScope.content;
						};

						/**
						 * Observe the relevant attributes.
						 */
						attrs.$observe('disabled', function (val) {
							if (val) {
								cancelShow();
							}

							if (val && ttScope.isOpen) {
								hide();
							}
						});

						if (isOpenParse) {
							scope.$watch(isOpenParse, function (val) {
								if (ttScope && !val === ttScope.isOpen) {
									toggleTooltipBind();
								}
							});
						}

						function prepObservers() {
							observers.length = 0;

							if (contentParse) {
								observers.push(
								  scope.$watch(contentParse, function (val) {
								  	ttScope.content = val;
								  	if (!val && ttScope.isOpen) {
								  		hide();
								  	}
								  })
								);

								observers.push(
								  tooltipLinkedScope.$watch(function () {
								  	if (!repositionScheduled) {
								  		repositionScheduled = true;
								  		tooltipLinkedScope.$$postDigest(function () {
								  			repositionScheduled = false;
								  			if (ttScope && ttScope.isOpen) {
								  				positionTooltip();
								  			}
								  		});
								  	}
								  })
								);
							} else {
								observers.push(
								  attrs.$observe(ttType, function (val) {
								  	ttScope.content = val;
								  	if (!val && ttScope.isOpen) {
								  		hide();
								  	} else {
								  		positionTooltip();
								  	}
								  })
								);
							}

							observers.push(
							  attrs.$observe(prefix + 'Title', function (val) {
							  	ttScope.title = val;
							  	if (ttScope.isOpen) {
							  		positionTooltip();
							  	}
							  })
							);

							observers.push(
							  attrs.$observe(prefix + 'Placement', function (val) {
							  	ttScope.placement = val ? val : options.placement;
							  	if (ttScope.isOpen) {
							  		positionTooltip();
							  	}
							  })
							);
						}

						function unregisterObservers() {
							if (observers.length) {
								angular.forEach(observers, function (observer) {
									observer();
								});
								observers.length = 0;
							}
						}

						// hide tooltips/popovers for outsideClick trigger
						function bodyHideTooltipBind(e) {
							if (!ttScope || !ttScope.isOpen || !tooltip) {
								return;
							}
							// make sure the tooltip/popover link or tool tooltip/popover itself were not clicked
							if (!element[0].contains(e.target) && !tooltip[0].contains(e.target)) {
								hideTooltipBind();
							}
						}

						var unregisterTriggers = function () {
							triggers.show.forEach(function (trigger) {
								if (trigger === 'outsideClick') {
									element.off('click', toggleTooltipBind);
								} else {
									element.off(trigger, showTooltipBind);
									element.off(trigger, toggleTooltipBind);
								}
							});
							triggers.hide.forEach(function (trigger) {
								if (trigger === 'outsideClick') {
									$document.off('click', bodyHideTooltipBind);
								} else {
									element.off(trigger, hideTooltipBind);
								}
							});
						};

						function prepTriggers() {
							var showTriggers = [], hideTriggers = [];
							var val = scope.$eval(attrs[prefix + 'Trigger']);
							unregisterTriggers();

							if (angular.isObject(val)) {
								Object.keys(val).forEach(function (key) {
									showTriggers.push(key);
									hideTriggers.push(val[key]);
								});
								triggers = {
									show: showTriggers,
									hide: hideTriggers
								};
							} else {
								triggers = getTriggers(val);
							}

							if (triggers.show !== 'none') {
								triggers.show.forEach(function (trigger, idx) {
									if (trigger === 'outsideClick') {
										element.on('click', toggleTooltipBind);
										$document.on('click', bodyHideTooltipBind);
									} else if (trigger === triggers.hide[idx]) {
										element.on(trigger, toggleTooltipBind);
									} else if (trigger) {
										element.on(trigger, showTooltipBind);
										element.on(triggers.hide[idx], hideTooltipBind);
									}

									element.on('keypress', function (e) {
										if (e.which === 27) {
											hideTooltipBind();
										}
									});
								});
							}
						}

						prepTriggers();

						var animation = scope.$eval(attrs[prefix + 'Animation']);
						ttScope.animation = angular.isDefined(animation) ? !!animation : options.animation;

						var appendToBodyVal;
						var appendKey = prefix + 'AppendToBody';
						if (appendKey in attrs && attrs[appendKey] === undefined) {
							appendToBodyVal = true;
						} else {
							appendToBodyVal = scope.$eval(attrs[appendKey]);
						}

						appendToBody = angular.isDefined(appendToBodyVal) ? appendToBodyVal : appendToBody;

						// Make sure tooltip is destroyed and removed.
						scope.$on('$destroy', function onDestroyTooltip() {
							unregisterTriggers();
							removeTooltip();
							ttScope = null;
						});
					};
				}
			};
		};
	}];
})

// This is mostly ngInclude code but with a custom scope
.directive('uibTooltipTemplateTransclude', [
         '$animate', '$sce', '$compile', '$templateRequest',
function ($animate, $sce, $compile, $templateRequest) {
	return {
		link: function (scope, elem, attrs) {
			var origScope = scope.$eval(attrs.tooltipTemplateTranscludeScope);

			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).then(function () {
						previousElement = null;
					});
					previousElement = currentElement;
					currentElement = null;
				}
			};

			scope.$watch($sce.parseAsResourceUrl(attrs.uibTooltipTemplateTransclude), function (src) {
				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 (thisChangeId !== changeCounter) { return; }
						var newScope = origScope.$new();
						var template = response;

						var clone = $compile(template)(newScope, function (clone) {
							cleanupLastIncludeContent();
							$animate.enter(clone, elem);
						});

						currentScope = newScope;
						currentElement = clone;

						currentScope.$emit('$includeContentLoaded', src);
					}, function () {
						if (thisChangeId === changeCounter) {
							cleanupLastIncludeContent();
							scope.$emit('$includeContentError', src);
						}
					});
					scope.$emit('$includeContentRequested', src);
				} else {
					cleanupLastIncludeContent();
				}
			});

			scope.$on('$destroy', cleanupLastIncludeContent);
		}
	};
}])

/**
 * Note that it's intentional that these classes are *not* applied through $animate.
 * They must not be animated as they're expected to be present on the tooltip on
 * initialization.
 */
.directive('uibTooltipClasses', ['$uibPosition', function ($uibPosition) {
	return {
		restrict: 'A',
		link: function (scope, element, attrs) {
			// need to set the primary position so the
			// arrow has space during position measure.
			// tooltip.positionTooltip()
			if (scope.placement) {
				// // There are no top-left etc... classes
				// // in TWBS, so we need the primary position.
				var position = $uibPosition.parsePlacement(scope.placement);
				element.addClass(position[0]);
			}

			if (scope.popupClass) {
				element.addClass(scope.popupClass);
			}

			if (scope.animation) {
				element.addClass(attrs.tooltipAnimationClass);
			}
		}
	};
}])

.directive('uibTooltipPopup', function () {
	return {
		restrict: 'A',
		scope: { content: '@' },
		templateUrl: 'uib/template/tooltip/tooltip-popup.html'
	};
})

.directive('uibTooltip', ['$uibTooltip', function ($uibTooltip) {
	return $uibTooltip('uibTooltip', 'tooltip', 'mouseenter');
}])

.directive('uibTooltipTemplatePopup', function () {
	return {
		restrict: 'A',
		scope: { contentExp: '&', originScope: '&' },
		templateUrl: 'uib/template/tooltip/tooltip-template-popup.html'
	};
})

.directive('uibTooltipTemplate', ['$uibTooltip', function ($uibTooltip) {
	return $uibTooltip('uibTooltipTemplate', 'tooltip', 'mouseenter', {
		useContentExp: true
	});
}])

.directive('uibTooltipHtmlPopup', function () {
	return {
		restrict: 'A',
		scope: { contentExp: '&' },
		templateUrl: 'uib/template/tooltip/tooltip-html-popup.html'
	};
})

.directive('uibTooltipHtml', ['$uibTooltip', function ($uibTooltip) {
	return $uibTooltip('uibTooltipHtml', 'tooltip', 'mouseenter', {
		useContentExp: true
	});
}]);

/**
 * The following features are still outstanding: popup delay, animation as a
 * function, placement as a function, inside, support for more triggers than
 * just mouse enter/leave, and selector delegatation.
 */
angular.module('ui.bootstrap.popover', ['ui.bootstrap.tooltip'])

.directive('uibPopoverTemplatePopup', function () {
	return {
		restrict: 'A',
		scope: { uibTitle: '@', contentExp: '&', originScope: '&' },
		templateUrl: 'uib/template/popover/popover-template.html'
	};
})

.directive('uibPopoverTemplate', ['$uibTooltip', function ($uibTooltip) {
	return $uibTooltip('uibPopoverTemplate', 'popover', 'click', {
		useContentExp: true
	});
}])

.directive('uibPopoverHtmlPopup', function () {
	return {
		restrict: 'A',
		scope: { contentExp: '&', uibTitle: '@' },
		templateUrl: 'uib/template/popover/popover-html.html'
	};
})

.directive('uibPopoverHtml', ['$uibTooltip', function ($uibTooltip) {
	return $uibTooltip('uibPopoverHtml', 'popover', 'click', {
		useContentExp: true
	});
}])

.directive('uibPopoverPopup', function () {
	return {
		restrict: 'A',
		scope: { uibTitle: '@', content: '@' },
		templateUrl: 'uib/template/popover/popover.html'
	};
})

.directive('uibPopover', ['$uibTooltip', function ($uibTooltip) {
	return $uibTooltip('uibPopover', 'popover', 'click');
}]);

angular.module('ui.bootstrap.progressbar', [])

.constant('uibProgressConfig', {
	animate: true,
	max: 100
})

.controller('UibProgressController', ['$scope', '$attrs', 'uibProgressConfig', function ($scope, $attrs, progressConfig) {
	var self = this,
		animate = angular.isDefined($attrs.animate) ? $scope.$parent.$eval($attrs.animate) : progressConfig.animate;

	this.bars = [];
	$scope.max = getMaxOrDefault();

	this.addBar = function (bar, element, attrs) {
		if (!animate) {
			element.css({ 'transition': 'none' });
		}

		this.bars.push(bar);

		bar.max = getMaxOrDefault();
		bar.title = attrs && angular.isDefined(attrs.title) ? attrs.title : 'progressbar';

		bar.$watch('value', function (value) {
			bar.recalculatePercentage();
		});

		bar.recalculatePercentage = function () {
			var totalPercentage = self.bars.reduce(function (total, bar) {
				bar.percent = +(100 * bar.value / bar.max).toFixed(2);
				return total + bar.percent;
			}, 0);

			if (totalPercentage > 100) {
				bar.percent -= totalPercentage - 100;
			}
		};

		bar.$on('$destroy', function () {
			element = null;
			self.removeBar(bar);
		});
	};

	this.removeBar = function (bar) {
		this.bars.splice(this.bars.indexOf(bar), 1);
		this.bars.forEach(function (bar) {
			bar.recalculatePercentage();
		});
	};

	//$attrs.$observe('maxParam', function(maxParam) {
	$scope.$watch('maxParam', function (maxParam) {
		self.bars.forEach(function (bar) {
			bar.max = getMaxOrDefault();
			bar.recalculatePercentage();
		});
	});

	function getMaxOrDefault() {
		return angular.isDefined($scope.maxParam) ? $scope.maxParam : progressConfig.max;
	}
}])

.directive('uibProgress', function () {
	return {
		replace: true,
		transclude: true,
		controller: 'UibProgressController',
		require: 'uibProgress',
		scope: {
			maxParam: '=?max'
		},
		templateUrl: 'uib/template/progressbar/progress.html'
	};
})

.directive('uibBar', function () {
	return {
		replace: true,
		transclude: true,
		require: '^uibProgress',
		scope: {
			value: '=',
			type: '@'
		},
		templateUrl: 'uib/template/progressbar/bar.html',
		link: function (scope, element, attrs, progressCtrl) {
			progressCtrl.addBar(scope, element, attrs);
		}
	};
})

.directive('uibProgressbar', function () {
	return {
		replace: true,
		transclude: true,
		controller: 'UibProgressController',
		scope: {
			value: '=',
			maxParam: '=?max',
			type: '@'
		},
		templateUrl: 'uib/template/progressbar/progressbar.html',
		link: function (scope, element, attrs, progressCtrl) {
			progressCtrl.addBar(scope, angular.element(element.children()[0]), { title: attrs.title });
		}
	};
});

angular.module('ui.bootstrap.rating', [])

.constant('uibRatingConfig', {
	max: 5,
	stateOn: null,
	stateOff: null,
	enableReset: true,
	titles: ['one', 'two', 'three', 'four', 'five']
})

.controller('UibRatingController', ['$scope', '$attrs', 'uibRatingConfig', function ($scope, $attrs, ratingConfig) {
	var ngModelCtrl = { $setViewValue: angular.noop },
	  self = this;

	this.init = function (ngModelCtrl_) {
		ngModelCtrl = ngModelCtrl_;
		ngModelCtrl.$render = this.render;

		ngModelCtrl.$formatters.push(function (value) {
			if (angular.isNumber(value) && value << 0 !== value) {
				value = Math.round(value);
			}

			return value;
		});

		this.stateOn = angular.isDefined($attrs.stateOn) ? $scope.$parent.$eval($attrs.stateOn) : ratingConfig.stateOn;
		this.stateOff = angular.isDefined($attrs.stateOff) ? $scope.$parent.$eval($attrs.stateOff) : ratingConfig.stateOff;
		this.enableReset = angular.isDefined($attrs.enableReset) ?
		  $scope.$parent.$eval($attrs.enableReset) : ratingConfig.enableReset;
		var tmpTitles = angular.isDefined($attrs.titles) ? $scope.$parent.$eval($attrs.titles) : ratingConfig.titles;
		this.titles = angular.isArray(tmpTitles) && tmpTitles.length > 0 ?
			tmpTitles : ratingConfig.titles;

		var ratingStates = angular.isDefined($attrs.ratingStates) ?
		  $scope.$parent.$eval($attrs.ratingStates) :
		  new Array(angular.isDefined($attrs.max) ? $scope.$parent.$eval($attrs.max) : ratingConfig.max);
		$scope.range = this.buildTemplateObjects(ratingStates);
	};

	this.buildTemplateObjects = function (states) {
		for (var i = 0, n = states.length; i < n; i++) {
			states[i] = angular.extend({ index: i }, { stateOn: this.stateOn, stateOff: this.stateOff, title: this.getTitle(i) }, states[i]);
		}
		return states;
	};

	this.getTitle = function (index) {
		if (index >= this.titles.length) {
			return index + 1;
		}

		return this.titles[index];
	};

	$scope.rate = function (value) {
		if (!$scope.readonly && value >= 0 && value <= $scope.range.length) {
			var newViewValue = self.enableReset && ngModelCtrl.$viewValue === value ? 0 : value;
			ngModelCtrl.$setViewValue(newViewValue);
			ngModelCtrl.$render();
		}
	};

	$scope.enter = function (value) {
		if (!$scope.readonly) {
			$scope.value = value;
		}
		$scope.onHover({ value: value });
	};

	$scope.reset = function () {
		$scope.value = ngModelCtrl.$viewValue;
		$scope.onLeave();
	};

	$scope.onKeydown = function (evt) {
		if (/(37|38|39|40)/.test(evt.which)) {
			evt.preventDefault();
			evt.stopPropagation();
			$scope.rate($scope.value + (evt.which === 38 || evt.which === 39 ? 1 : -1));
		}
	};

	this.render = function () {
		$scope.value = ngModelCtrl.$viewValue;
		$scope.title = self.getTitle($scope.value - 1);
	};
}])

.directive('uibRating', function () {
	return {
		require: ['uibRating', 'ngModel'],
		restrict: 'A',
		scope: {
			readonly: '=?readOnly',
			onHover: '&',
			onLeave: '&'
		},
		controller: 'UibRatingController',
		templateUrl: 'uib/template/rating/rating.html',
		link: function (scope, element, attrs, ctrls) {
			var ratingCtrl = ctrls[0], ngModelCtrl = ctrls[1];
			ratingCtrl.init(ngModelCtrl);
		}
	};
});

angular.module('ui.bootstrap.tabs', [])

.controller('UibTabsetController', ['$scope', function ($scope) {
	var ctrl = this,
	  oldIndex;
	ctrl.tabs = [];

	ctrl.select = function (index, evt) {
		if (!destroyed) {
			var previousIndex = findTabIndex(oldIndex);
			var previousSelected = ctrl.tabs[previousIndex];
			if (previousSelected) {
				previousSelected.tab.onDeselect({
					$event: evt,
					$selectedIndex: index
				});
				if (evt && evt.isDefaultPrevented()) {
					return;
				}
				previousSelected.tab.active = false;
			}

			var selected = ctrl.tabs[index];
			if (selected) {
				selected.tab.onSelect({
					$event: evt
				});
				selected.tab.active = true;
				ctrl.active = selected.index;
				oldIndex = selected.index;
			} else if (!selected && angular.isDefined(oldIndex)) {
				ctrl.active = null;
				oldIndex = null;
			}
		}
	};

	ctrl.addTab = function addTab(tab) {
		ctrl.tabs.push({
			tab: tab,
			index: tab.index
		});
		ctrl.tabs.sort(function (t1, t2) {
			if (t1.index > t2.index) {
				return 1;
			}

			if (t1.index < t2.index) {
				return -1;
			}

			return 0;
		});

		if (tab.index === ctrl.active || !angular.isDefined(ctrl.active) && ctrl.tabs.length === 1) {
			var newActiveIndex = findTabIndex(tab.index);
			ctrl.select(newActiveIndex);
		}
	};

	ctrl.removeTab = function removeTab(tab) {
		var index;
		for (var i = 0; i < ctrl.tabs.length; i++) {
			if (ctrl.tabs[i].tab === tab) {
				index = i;
				break;
			}
		}

		if (ctrl.tabs[index].index === ctrl.active) {
			var newActiveTabIndex = index === ctrl.tabs.length - 1 ?
			  index - 1 : index + 1 % ctrl.tabs.length;
			ctrl.select(newActiveTabIndex);
		}

		ctrl.tabs.splice(index, 1);
	};

	$scope.$watch('tabset.active', function (val) {
		if (angular.isDefined(val) && val !== oldIndex) {
			ctrl.select(findTabIndex(val));
		}
	});

	var destroyed;
	$scope.$on('$destroy', function () {
		destroyed = true;
	});

	function findTabIndex(index) {
		for (var i = 0; i < ctrl.tabs.length; i++) {
			if (ctrl.tabs[i].index === index) {
				return i;
			}
		}
	}
}])

.directive('uibTabset', function () {
	return {
		transclude: true,
		replace: true,
		scope: {},
		bindToController: {
			active: '=?',
			type: '@'
		},
		controller: 'UibTabsetController',
		controllerAs: 'tabset',
		templateUrl: function (element, attrs) {
			return attrs.templateUrl || 'uib/template/tabs/tabset.html';
		},
		link: function (scope, element, attrs) {
			scope.vertical = angular.isDefined(attrs.vertical) ?
			  scope.$parent.$eval(attrs.vertical) : false;
			scope.justified = angular.isDefined(attrs.justified) ?
			  scope.$parent.$eval(attrs.justified) : false;
		}
	};
})

.directive('uibTab', ['$parse', function ($parse) {
	return {
		require: '^uibTabset',
		replace: true,
		templateUrl: function (element, attrs) {
			return attrs.templateUrl || 'uib/template/tabs/tab.html';
		},
		transclude: true,
		scope: {
			heading: '@',
			index: '=?',
			classes: '@?',
			onSelect: '&select', //This callback is called in contentHeadingTransclude
			//once it inserts the tab's content into the dom
			onDeselect: '&deselect'
		},
		controller: function () {
			//Empty controller so other directives can require being 'under' a tab
		},
		controllerAs: 'tab',
		link: function (scope, elm, attrs, tabsetCtrl, transclude) {
			scope.disabled = false;
			if (attrs.disable) {
				scope.$parent.$watch($parse(attrs.disable), function (value) {
					scope.disabled = !!value;
				});
			}

			if (angular.isUndefined(attrs.index)) {
				if (tabsetCtrl.tabs && tabsetCtrl.tabs.length) {
					scope.index = Math.max.apply(null, tabsetCtrl.tabs.map(function (t) { return t.index; })) + 1;
				} else {
					scope.index = 0;
				}
			}

			if (angular.isUndefined(attrs.classes)) {
				scope.classes = '';
			}

			scope.select = function (evt) {
				if (!scope.disabled) {
					var index;
					for (var i = 0; i < tabsetCtrl.tabs.length; i++) {
						if (tabsetCtrl.tabs[i].tab === scope) {
							index = i;
							break;
						}
					}

					tabsetCtrl.select(index, evt);
				}
			};

			tabsetCtrl.addTab(scope);
			scope.$on('$destroy', function () {
				tabsetCtrl.removeTab(scope);
			});

			//We need to transclude later, once the content container is ready.
			//when this link happens, we're inside a tab heading.
			scope.$transcludeFn = transclude;
		}
	};
}])

.directive('uibTabHeadingTransclude', function () {
	return {
		restrict: 'A',
		require: '^uibTab',
		link: function (scope, elm) {
			scope.$watch('headingElement', function updateHeadingElement(heading) {
				if (heading) {
					elm.html('');
					elm.append(heading);
				}
			});
		}
	};
})

.directive('uibTabContentTransclude', function () {
	return {
		restrict: 'A',
		require: '^uibTabset',
		link: function (scope, elm, attrs) {
			var tab = scope.$eval(attrs.uibTabContentTransclude).tab;

			//Now our tab is ready to be transcluded: both the tab heading area
			//and the tab content area are loaded.  Transclude 'em both.
			tab.$transcludeFn(tab.$parent, function (contents) {
				angular.forEach(contents, function (node) {
					if (isTabHeading(node)) {
						//Let tabHeadingTransclude know.
						tab.headingElement = node;
					} else {
						elm.append(node);
					}
				});
			});
		}
	};

	function isTabHeading(node) {
		return node.tagName && (
		  node.hasAttribute('uib-tab-heading') ||
		  node.hasAttribute('data-uib-tab-heading') ||
		  node.hasAttribute('x-uib-tab-heading') ||
		  node.tagName.toLowerCase() === 'uib-tab-heading' ||
		  node.tagName.toLowerCase() === 'data-uib-tab-heading' ||
		  node.tagName.toLowerCase() === 'x-uib-tab-heading' ||
		  node.tagName.toLowerCase() === 'uib:tab-heading'
		);
	}
});

angular.module('ui.bootstrap.timepicker', [])

.constant('uibTimepickerConfig', {
	hourStep: 1,
	minuteStep: 1,
	secondStep: 1,
	showMeridian: true,
	showSeconds: false,
	meridians: null,
	readonlyInput: false,
	mousewheel: true,
	arrowkeys: true,
	showSpinners: true,
	templateUrl: 'uib/template/timepicker/timepicker.html'
})

.controller('UibTimepickerController', ['$scope', '$element', '$attrs', '$parse', '$log', '$locale', 'uibTimepickerConfig', function ($scope, $element, $attrs, $parse, $log, $locale, timepickerConfig) {
	var selected = new Date(),
	  watchers = [],
	  ngModelCtrl = { $setViewValue: angular.noop }, // nullModelCtrl
	  meridians = angular.isDefined($attrs.meridians) ? $scope.$parent.$eval($attrs.meridians) : timepickerConfig.meridians || $locale.DATETIME_FORMATS.AMPMS,
	  padHours = angular.isDefined($attrs.padHours) ? $scope.$parent.$eval($attrs.padHours) : true;

	$scope.tabindex = angular.isDefined($attrs.tabindex) ? $attrs.tabindex : 0;
	$element.removeAttr('tabindex');

	this.init = function (ngModelCtrl_, inputs) {
		ngModelCtrl = ngModelCtrl_;
		ngModelCtrl.$render = this.render;

		ngModelCtrl.$formatters.unshift(function (modelValue) {
			return modelValue ? new Date(modelValue) : null;
		});

		var hoursInputEl = inputs.eq(0),
			minutesInputEl = inputs.eq(1),
			secondsInputEl = inputs.eq(2);

		var mousewheel = angular.isDefined($attrs.mousewheel) ? $scope.$parent.$eval($attrs.mousewheel) : timepickerConfig.mousewheel;

		if (mousewheel) {
			this.setupMousewheelEvents(hoursInputEl, minutesInputEl, secondsInputEl);
		}

		var arrowkeys = angular.isDefined($attrs.arrowkeys) ? $scope.$parent.$eval($attrs.arrowkeys) : timepickerConfig.arrowkeys;
		if (arrowkeys) {
			this.setupArrowkeyEvents(hoursInputEl, minutesInputEl, secondsInputEl);
		}

		$scope.readonlyInput = angular.isDefined($attrs.readonlyInput) ? $scope.$parent.$eval($attrs.readonlyInput) : timepickerConfig.readonlyInput;
		this.setupInputEvents(hoursInputEl, minutesInputEl, secondsInputEl);
	};

	var hourStep = timepickerConfig.hourStep;
	if ($attrs.hourStep) {
		watchers.push($scope.$parent.$watch($parse($attrs.hourStep), function (value) {
			hourStep = +value;
		}));
	}

	var minuteStep = timepickerConfig.minuteStep;
	if ($attrs.minuteStep) {
		watchers.push($scope.$parent.$watch($parse($attrs.minuteStep), function (value) {
			minuteStep = +value;
		}));
	}

	var min;
	watchers.push($scope.$parent.$watch($parse($attrs.min), function (value) {
		var dt = new Date(value);
		min = isNaN(dt) ? undefined : dt;
	}));

	var max;
	watchers.push($scope.$parent.$watch($parse($attrs.max), function (value) {
		var dt = new Date(value);
		max = isNaN(dt) ? undefined : dt;
	}));

	var disabled = false;
	if ($attrs.ngDisabled) {
		watchers.push($scope.$parent.$watch($parse($attrs.ngDisabled), function (value) {
			disabled = value;
		}));
	}

	$scope.noIncrementHours = function () {
		var incrementedSelected = addMinutes(selected, hourStep * 60);
		return disabled || incrementedSelected > max ||
		  incrementedSelected < selected && incrementedSelected < min;
	};

	$scope.noDecrementHours = function () {
		var decrementedSelected = addMinutes(selected, -hourStep * 60);
		return disabled || decrementedSelected < min ||
		  decrementedSelected > selected && decrementedSelected > max;
	};

	$scope.noIncrementMinutes = function () {
		var incrementedSelected = addMinutes(selected, minuteStep);
		return disabled || incrementedSelected > max ||
		  incrementedSelected < selected && incrementedSelected < min;
	};

	$scope.noDecrementMinutes = function () {
		var decrementedSelected = addMinutes(selected, -minuteStep);
		return disabled || decrementedSelected < min ||
		  decrementedSelected > selected && decrementedSelected > max;
	};

	$scope.noIncrementSeconds = function () {
		var incrementedSelected = addSeconds(selected, secondStep);
		return disabled || incrementedSelected > max ||
		  incrementedSelected < selected && incrementedSelected < min;
	};

	$scope.noDecrementSeconds = function () {
		var decrementedSelected = addSeconds(selected, -secondStep);
		return disabled || decrementedSelected < min ||
		  decrementedSelected > selected && decrementedSelected > max;
	};

	$scope.noToggleMeridian = function () {
		if (selected.getHours() < 12) {
			return disabled || addMinutes(selected, 12 * 60) > max;
		}

		return disabled || addMinutes(selected, -12 * 60) < min;
	};

	var secondStep = timepickerConfig.secondStep;
	if ($attrs.secondStep) {
		watchers.push($scope.$parent.$watch($parse($attrs.secondStep), function (value) {
			secondStep = +value;
		}));
	}

	$scope.showSeconds = timepickerConfig.showSeconds;
	if ($attrs.showSeconds) {
		watchers.push($scope.$parent.$watch($parse($attrs.showSeconds), function (value) {
			$scope.showSeconds = !!value;
		}));
	}

	// 12H / 24H mode
	$scope.showMeridian = timepickerConfig.showMeridian;
	if ($attrs.showMeridian) {
		watchers.push($scope.$parent.$watch($parse($attrs.showMeridian), function (value) {
			$scope.showMeridian = !!value;

			if (ngModelCtrl.$error.time) {
				// Evaluate from template
				var hours = getHoursFromTemplate(), minutes = getMinutesFromTemplate();
				if (angular.isDefined(hours) && angular.isDefined(minutes)) {
					selected.setHours(hours);
					refresh();
				}
			} else {
				updateTemplate();
			}
		}));
	}

	// Get $scope.hours in 24H mode if valid
	function getHoursFromTemplate() {
		var hours = +$scope.hours;
		var valid = $scope.showMeridian ? hours > 0 && hours < 13 :
		  hours >= 0 && hours < 24;
		if (!valid || $scope.hours === '') {
			return undefined;
		}

		if ($scope.showMeridian) {
			if (hours === 12) {
				hours = 0;
			}
			if ($scope.meridian === meridians[1]) {
				hours = hours + 12;
			}
		}
		return hours;
	}

	function getMinutesFromTemplate() {
		var minutes = +$scope.minutes;
		var valid = minutes >= 0 && minutes < 60;
		if (!valid || $scope.minutes === '') {
			return undefined;
		}
		return minutes;
	}

	function getSecondsFromTemplate() {
		var seconds = +$scope.seconds;
		return seconds >= 0 && seconds < 60 ? seconds : undefined;
	}

	function pad(value, noPad) {
		if (value === null) {
			return '';
		}

		return angular.isDefined(value) && value.toString().length < 2 && !noPad ?
		  '0' + value : value.toString();
	}

	// Respond on mousewheel spin
	this.setupMousewheelEvents = function (hoursInputEl, minutesInputEl, secondsInputEl) {
		var isScrollingUp = function (e) {
			if (e.originalEvent) {
				e = e.originalEvent;
			}
			//pick correct delta variable depending on event
			var delta = e.wheelDelta ? e.wheelDelta : -e.deltaY;
			return e.detail || delta > 0;
		};

		hoursInputEl.bind('mousewheel wheel', function (e) {
			if (!disabled) {
				$scope.$apply(isScrollingUp(e) ? $scope.incrementHours() : $scope.decrementHours());
			}
			e.preventDefault();
		});

		minutesInputEl.bind('mousewheel wheel', function (e) {
			if (!disabled) {
				$scope.$apply(isScrollingUp(e) ? $scope.incrementMinutes() : $scope.decrementMinutes());
			}
			e.preventDefault();
		});

		secondsInputEl.bind('mousewheel wheel', function (e) {
			if (!disabled) {
				$scope.$apply(isScrollingUp(e) ? $scope.incrementSeconds() : $scope.decrementSeconds());
			}
			e.preventDefault();
		});
	};

	// Respond on up/down arrowkeys
	this.setupArrowkeyEvents = function (hoursInputEl, minutesInputEl, secondsInputEl) {
		hoursInputEl.bind('keydown', function (e) {
			if (!disabled) {
				if (e.which === 38) { // up
					e.preventDefault();
					$scope.incrementHours();
					$scope.$apply();
				} else if (e.which === 40) { // down
					e.preventDefault();
					$scope.decrementHours();
					$scope.$apply();
				}
			}
		});

		minutesInputEl.bind('keydown', function (e) {
			if (!disabled) {
				if (e.which === 38) { // up
					e.preventDefault();
					$scope.incrementMinutes();
					$scope.$apply();
				} else if (e.which === 40) { // down
					e.preventDefault();
					$scope.decrementMinutes();
					$scope.$apply();
				}
			}
		});

		secondsInputEl.bind('keydown', function (e) {
			if (!disabled) {
				if (e.which === 38) { // up
					e.preventDefault();
					$scope.incrementSeconds();
					$scope.$apply();
				} else if (e.which === 40) { // down
					e.preventDefault();
					$scope.decrementSeconds();
					$scope.$apply();
				}
			}
		});
	};

	this.setupInputEvents = function (hoursInputEl, minutesInputEl, secondsInputEl) {
		if ($scope.readonlyInput) {
			$scope.updateHours = angular.noop;
			$scope.updateMinutes = angular.noop;
			$scope.updateSeconds = angular.noop;
			return;
		}

		var invalidate = function (invalidHours, invalidMinutes, invalidSeconds) {
			ngModelCtrl.$setViewValue(null);
			ngModelCtrl.$setValidity('time', false);
			if (angular.isDefined(invalidHours)) {
				$scope.invalidHours = invalidHours;
			}

			if (angular.isDefined(invalidMinutes)) {
				$scope.invalidMinutes = invalidMinutes;
			}

			if (angular.isDefined(invalidSeconds)) {
				$scope.invalidSeconds = invalidSeconds;
			}
		};

		$scope.updateHours = function () {
			var hours = getHoursFromTemplate(),
			  minutes = getMinutesFromTemplate();

			ngModelCtrl.$setDirty();

			if (angular.isDefined(hours) && angular.isDefined(minutes)) {
				selected.setHours(hours);
				selected.setMinutes(minutes);
				if (selected < min || selected > max) {
					invalidate(true);
				} else {
					refresh('h');
				}
			} else {
				invalidate(true);
			}
		};

		hoursInputEl.bind('blur', function (e) {
			ngModelCtrl.$setTouched();
			if (modelIsEmpty()) {
				makeValid();
			} else if ($scope.hours === null || $scope.hours === '') {
				invalidate(true);
			} else if (!$scope.invalidHours && $scope.hours < 10) {
				$scope.$apply(function () {
					$scope.hours = pad($scope.hours, !padHours);
				});
			}
		});

		$scope.updateMinutes = function () {
			var minutes = getMinutesFromTemplate(),
			  hours = getHoursFromTemplate();

			ngModelCtrl.$setDirty();

			if (angular.isDefined(minutes) && angular.isDefined(hours)) {
				selected.setHours(hours);
				selected.setMinutes(minutes);
				if (selected < min || selected > max) {
					invalidate(undefined, true);
				} else {
					refresh('m');
				}
			} else {
				invalidate(undefined, true);
			}
		};

		minutesInputEl.bind('blur', function (e) {
			ngModelCtrl.$setTouched();
			if (modelIsEmpty()) {
				makeValid();
			} else if ($scope.minutes === null) {
				invalidate(undefined, true);
			} else if (!$scope.invalidMinutes && $scope.minutes < 10) {
				$scope.$apply(function () {
					$scope.minutes = pad($scope.minutes);
				});
			}
		});

		$scope.updateSeconds = function () {
			var seconds = getSecondsFromTemplate();

			ngModelCtrl.$setDirty();

			if (angular.isDefined(seconds)) {
				selected.setSeconds(seconds);
				refresh('s');
			} else {
				invalidate(undefined, undefined, true);
			}
		};

		secondsInputEl.bind('blur', function (e) {
			if (modelIsEmpty()) {
				makeValid();
			} else if (!$scope.invalidSeconds && $scope.seconds < 10) {
				$scope.$apply(function () {
					$scope.seconds = pad($scope.seconds);
				});
			}
		});

	};

	this.render = function () {
		var date = ngModelCtrl.$viewValue;

		if (isNaN(date)) {
			ngModelCtrl.$setValidity('time', false);
			$log.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.');
		} else {
			if (date) {
				selected = date;
			}

			if (selected < min || selected > max) {
				ngModelCtrl.$setValidity('time', false);
				$scope.invalidHours = true;
				$scope.invalidMinutes = true;
			} else {
				makeValid();
			}
			updateTemplate();
		}
	};

	// Call internally when we know that model is valid.
	function refresh(keyboardChange) {
		makeValid();
		ngModelCtrl.$setViewValue(new Date(selected));
		updateTemplate(keyboardChange);
	}

	function makeValid() {
		ngModelCtrl.$setValidity('time', true);
		$scope.invalidHours = false;
		$scope.invalidMinutes = false;
		$scope.invalidSeconds = false;
	}

	function updateTemplate(keyboardChange) {
		if (!ngModelCtrl.$modelValue) {
			$scope.hours = null;
			$scope.minutes = null;
			$scope.seconds = null;
			$scope.meridian = meridians[0];
		} else {
			var hours = selected.getHours(),
			  minutes = selected.getMinutes(),
			  seconds = selected.getSeconds();

			if ($scope.showMeridian) {
				hours = hours === 0 || hours === 12 ? 12 : hours % 12; // Convert 24 to 12 hour system
			}

			$scope.hours = keyboardChange === 'h' ? hours : pad(hours, !padHours);
			if (keyboardChange !== 'm') {
				$scope.minutes = pad(minutes);
			}
			$scope.meridian = selected.getHours() < 12 ? meridians[0] : meridians[1];

			if (keyboardChange !== 's') {
				$scope.seconds = pad(seconds);
			}
			$scope.meridian = selected.getHours() < 12 ? meridians[0] : meridians[1];
		}
	}

	function addSecondsToSelected(seconds) {
		selected = addSeconds(selected, seconds);
		refresh();
	}

	function addMinutes(selected, minutes) {
		return addSeconds(selected, minutes * 60);
	}

	function addSeconds(date, seconds) {
		var dt = new Date(date.getTime() + seconds * 1000);
		var newDate = new Date(date);
		newDate.setHours(dt.getHours(), dt.getMinutes(), dt.getSeconds());
		return newDate;
	}

	function modelIsEmpty() {
		return ($scope.hours === null || $scope.hours === '') &&
		  ($scope.minutes === null || $scope.minutes === '') &&
		  (!$scope.showSeconds || $scope.showSeconds && ($scope.seconds === null || $scope.seconds === ''));
	}

	$scope.showSpinners = angular.isDefined($attrs.showSpinners) ?
	  $scope.$parent.$eval($attrs.showSpinners) : timepickerConfig.showSpinners;

	$scope.incrementHours = function () {
		if (!$scope.noIncrementHours()) {
			addSecondsToSelected(hourStep * 60 * 60);
		}
	};

	$scope.decrementHours = function () {
		if (!$scope.noDecrementHours()) {
			addSecondsToSelected(-hourStep * 60 * 60);
		}
	};

	$scope.incrementMinutes = function () {
		if (!$scope.noIncrementMinutes()) {
			addSecondsToSelected(minuteStep * 60);
		}
	};

	$scope.decrementMinutes = function () {
		if (!$scope.noDecrementMinutes()) {
			addSecondsToSelected(-minuteStep * 60);
		}
	};

	$scope.incrementSeconds = function () {
		if (!$scope.noIncrementSeconds()) {
			addSecondsToSelected(secondStep);
		}
	};

	$scope.decrementSeconds = function () {
		if (!$scope.noDecrementSeconds()) {
			addSecondsToSelected(-secondStep);
		}
	};

	$scope.toggleMeridian = function () {
		var minutes = getMinutesFromTemplate(),
			hours = getHoursFromTemplate();

		if (!$scope.noToggleMeridian()) {
			if (angular.isDefined(minutes) && angular.isDefined(hours)) {
				addSecondsToSelected(12 * 60 * (selected.getHours() < 12 ? 60 : -60));
			} else {
				$scope.meridian = $scope.meridian === meridians[0] ? meridians[1] : meridians[0];
			}
		}
	};

	$scope.blur = function () {
		ngModelCtrl.$setTouched();
	};

	$scope.$on('$destroy', function () {
		while (watchers.length) {
			watchers.shift()();
		}
	});
}])

.directive('uibTimepicker', ['uibTimepickerConfig', function (uibTimepickerConfig) {
	return {
		require: ['uibTimepicker', '?^ngModel'],
		restrict: 'A',
		controller: 'UibTimepickerController',
		controllerAs: 'timepicker',
		scope: {},
		templateUrl: function (element, attrs) {
			return attrs.templateUrl || uibTimepickerConfig.templateUrl;
		},
		link: function (scope, element, attrs, ctrls) {
			var timepickerCtrl = ctrls[0], ngModelCtrl = ctrls[1];

			if (ngModelCtrl) {
				timepickerCtrl.init(ngModelCtrl, element.find('input'));
			}
		}
	};
}]);

angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.debounce', 'ui.bootstrap.position'])

/**
 * A helper service that can parse typeahead's syntax (string provided by users)
 * Extracted to a separate service for ease of unit testing
 */
  .factory('uibTypeaheadParser', ['$parse', function ($parse) {
  	//                      000001111111100000000000002222222200000000000000003333333333333330000000000044444444000
  	var TYPEAHEAD_REGEXP = /^\s*([\s\S]+?)(?:\s+as\s+([\s\S]+?))?\s+for\s+(?:([\$\w][\$\w\d]*))\s+in\s+([\s\S]+?)$/;
  	return {
  		parse: function (input) {
  			var match = input.match(TYPEAHEAD_REGEXP);
  			if (!match) {
  				throw new Error(
				  'Expected typeahead specification in form of "_modelValue_ (as _label_)? for _item_ in _collection_"' +
					' but got "' + input + '".');
  			}

  			return {
  				itemName: match[3],
  				source: $parse(match[4]),
  				viewMapper: $parse(match[2] || match[1]),
  				modelMapper: $parse(match[1])
  			};
  		}
  	};
  }])

  .controller('UibTypeaheadController', ['$scope', '$element', '$attrs', '$compile', '$parse', '$q', '$timeout', '$document', '$window', '$rootScope', '$$debounce', '$uibPosition', 'uibTypeaheadParser',
    function (originalScope, element, attrs, $compile, $parse, $q, $timeout, $document, $window, $rootScope, $$debounce, $position, typeaheadParser) {
    	var HOT_KEYS = [9, 13, 27, 38, 40];
    	var eventDebounceTime = 200;
    	var modelCtrl, ngModelOptions;
    	//SUPPORTED ATTRIBUTES (OPTIONS)

    	//minimal no of characters that needs to be entered before typeahead kicks-in
    	var minLength = originalScope.$eval(attrs.typeaheadMinLength);
    	if (!minLength && minLength !== 0) {
    		minLength = 1;
    	}

    	originalScope.$watch(attrs.typeaheadMinLength, function (newVal) {
    		minLength = !newVal && newVal !== 0 ? 1 : newVal;
    	});

    	//minimal wait time after last character typed before typeahead kicks-in
    	var waitTime = originalScope.$eval(attrs.typeaheadWaitMs) || 0;

    	//should it restrict model values to the ones selected from the popup only?
    	var isEditable = originalScope.$eval(attrs.typeaheadEditable) !== false;
    	originalScope.$watch(attrs.typeaheadEditable, function (newVal) {
    		isEditable = newVal !== false;
    	});

    	//binding to a variable that indicates if matches are being retrieved asynchronously
    	var isLoadingSetter = $parse(attrs.typeaheadLoading).assign || angular.noop;

    	//a function to determine if an event should cause selection
    	var isSelectEvent = attrs.typeaheadShouldSelect ? $parse(attrs.typeaheadShouldSelect) : function (scope, vals) {
    		var evt = vals.$event;
    		return evt.which === 13 || evt.which === 9;
    	};

    	//a callback executed when a match is selected
    	var onSelectCallback = $parse(attrs.typeaheadOnSelect);

    	//should it select highlighted popup value when losing focus?
    	var isSelectOnBlur = angular.isDefined(attrs.typeaheadSelectOnBlur) ? originalScope.$eval(attrs.typeaheadSelectOnBlur) : false;

    	//binding to a variable that indicates if there were no results after the query is completed
    	var isNoResultsSetter = $parse(attrs.typeaheadNoResults).assign || angular.noop;

    	var inputFormatter = attrs.typeaheadInputFormatter ? $parse(attrs.typeaheadInputFormatter) : undefined;

    	var appendToBody = attrs.typeaheadAppendToBody ? originalScope.$eval(attrs.typeaheadAppendToBody) : false;

    	var appendTo = attrs.typeaheadAppendTo ?
		  originalScope.$eval(attrs.typeaheadAppendTo) : null;

    	var focusFirst = originalScope.$eval(attrs.typeaheadFocusFirst) !== false;

    	//If input matches an item of the list exactly, select it automatically
    	var selectOnExact = attrs.typeaheadSelectOnExact ? originalScope.$eval(attrs.typeaheadSelectOnExact) : false;

    	//binding to a variable that indicates if dropdown is open
    	var isOpenSetter = $parse(attrs.typeaheadIsOpen).assign || angular.noop;

    	var showHint = originalScope.$eval(attrs.typeaheadShowHint) || false;

    	//INTERNAL VARIABLES

    	//model setter executed upon match selection
    	var parsedModel = $parse(attrs.ngModel);
    	var invokeModelSetter = $parse(attrs.ngModel + '($$$p)');
    	var $setModelValue = function (scope, newValue) {
    		if (angular.isFunction(parsedModel(originalScope)) &&
			  ngModelOptions && ngModelOptions.$options && ngModelOptions.$options.getterSetter) {
    			return invokeModelSetter(scope, { $$$p: newValue });
    		}

    		return parsedModel.assign(scope, newValue);
    	};

    	//expressions used by typeahead
    	var parserResult = typeaheadParser.parse(attrs.uibTypeahead);

    	var hasFocus;

    	//Used to avoid bug in iOS webview where iOS keyboard does not fire
    	//mousedown & mouseup events
    	//Issue #3699
    	var selected;

    	//create a child scope for the typeahead directive so we are not polluting original scope
    	//with typeahead-specific data (matches, query etc.)
    	var scope = originalScope.$new();
    	var offDestroy = originalScope.$on('$destroy', function () {
    		scope.$destroy();
    	});
    	scope.$on('$destroy', offDestroy);

    	// WAI-ARIA
    	var popupId = 'typeahead-' + scope.$id + '-' + Math.floor(Math.random() * 10000);
    	element.attr({
    		'aria-autocomplete': 'list',
    		'aria-expanded': false,
    		'aria-owns': popupId
    	});

    	var inputsContainer, hintInputElem;
    	//add read-only input to show hint
    	if (showHint) {
    		inputsContainer = angular.element('<div></div>');
    		inputsContainer.css('position', 'relative');
    		element.after(inputsContainer);
    		hintInputElem = element.clone();
    		hintInputElem.attr('placeholder', '');
    		hintInputElem.attr('tabindex', '-1');
    		hintInputElem.val('');
    		hintInputElem.css({
    			'position': 'absolute',
    			'top': '0px',
    			'left': '0px',
    			'border-color': 'transparent',
    			'box-shadow': 'none',
    			'opacity': 1,
    			'background': 'none 0% 0% / auto repeat scroll padding-box border-box rgb(255, 255, 255)',
    			'color': '#999'
    		});
    		element.css({
    			'position': 'relative',
    			'vertical-align': 'top',
    			'background-color': 'transparent'
    		});

    		if (hintInputElem.attr('id')) {
    			hintInputElem.removeAttr('id'); // remove duplicate id if present.
    		}
    		inputsContainer.append(hintInputElem);
    		hintInputElem.after(element);
    	}

    	//pop-up element used to display matches
    	var popUpEl = angular.element('<div uib-typeahead-popup></div>');
    	popUpEl.attr({
    		id: popupId,
    		matches: 'matches',
    		active: 'activeIdx',
    		select: 'select(activeIdx, evt)',
    		'move-in-progress': 'moveInProgress',
    		query: 'query',
    		position: 'position',
    		'assign-is-open': 'assignIsOpen(isOpen)',
    		debounce: 'debounceUpdate'
    	});
    	//custom item template
    	if (angular.isDefined(attrs.typeaheadTemplateUrl)) {
    		popUpEl.attr('template-url', attrs.typeaheadTemplateUrl);
    	}

    	if (angular.isDefined(attrs.typeaheadPopupTemplateUrl)) {
    		popUpEl.attr('popup-template-url', attrs.typeaheadPopupTemplateUrl);
    	}

    	var resetHint = function () {
    		if (showHint) {
    			hintInputElem.val('');
    		}
    	};

    	var resetMatches = function () {
    		scope.matches = [];
    		scope.activeIdx = -1;
    		element.attr('aria-expanded', false);
    		resetHint();
    	};

    	var getMatchId = function (index) {
    		return popupId + '-option-' + index;
    	};

    	// Indicate that the specified match is the active (pre-selected) item in the list owned by this typeahead.
    	// This attribute is added or removed automatically when the `activeIdx` changes.
    	scope.$watch('activeIdx', function (index) {
    		if (index < 0) {
    			element.removeAttr('aria-activedescendant');
    		} else {
    			element.attr('aria-activedescendant', getMatchId(index));
    		}
    	});

    	var inputIsExactMatch = function (inputValue, index) {
    		if (scope.matches.length > index && inputValue) {
    			return inputValue.toUpperCase() === scope.matches[index].label.toUpperCase();
    		}

    		return false;
    	};

    	var getMatchesAsync = function (inputValue, evt) {
    		var locals = { $viewValue: inputValue };
    		isLoadingSetter(originalScope, true);
    		isNoResultsSetter(originalScope, false);
    		$q.when(parserResult.source(originalScope, locals)).then(function (matches) {
    			//it might happen that several async queries were in progress if a user were typing fast
    			//but we are interested only in responses that correspond to the current view value
    			var onCurrentRequest = inputValue === modelCtrl.$viewValue;
    			if (onCurrentRequest && hasFocus) {
    				if (matches && matches.length > 0) {
    					scope.activeIdx = focusFirst ? 0 : -1;
    					isNoResultsSetter(originalScope, false);
    					scope.matches.length = 0;

    					//transform labels
    					for (var i = 0; i < matches.length; i++) {
    						locals[parserResult.itemName] = matches[i];
    						scope.matches.push({
    							id: getMatchId(i),
    							label: parserResult.viewMapper(scope, locals),
    							model: matches[i]
    						});
    					}

    					scope.query = inputValue;
    					//position pop-up with matches - we need to re-calculate its position each time we are opening a window
    					//with matches as a pop-up might be absolute-positioned and position of an input might have changed on a page
    					//due to other elements being rendered
    					recalculatePosition();

    					element.attr('aria-expanded', true);

    					//Select the single remaining option if user input matches
    					if (selectOnExact && scope.matches.length === 1 && inputIsExactMatch(inputValue, 0)) {
    						if (angular.isNumber(scope.debounceUpdate) || angular.isObject(scope.debounceUpdate)) {
    							$$debounce(function () {
    								scope.select(0, evt);
    							}, angular.isNumber(scope.debounceUpdate) ? scope.debounceUpdate : scope.debounceUpdate['default']);
    						} else {
    							scope.select(0, evt);
    						}
    					}

    					if (showHint) {
    						var firstLabel = scope.matches[0].label;
    						if (angular.isString(inputValue) &&
							  inputValue.length > 0 &&
							  firstLabel.slice(0, inputValue.length).toUpperCase() === inputValue.toUpperCase()) {
    							hintInputElem.val(inputValue + firstLabel.slice(inputValue.length));
    						} else {
    							hintInputElem.val('');
    						}
    					}
    				} else {
    					resetMatches();
    					isNoResultsSetter(originalScope, true);
    				}
    			}
    			if (onCurrentRequest) {
    				isLoadingSetter(originalScope, false);
    			}
    		}, function () {
    			resetMatches();
    			isLoadingSetter(originalScope, false);
    			isNoResultsSetter(originalScope, true);
    		});
    	};

    	// bind events only if appendToBody params exist - performance feature
    	if (appendToBody) {
    		angular.element($window).on('resize', fireRecalculating);
    		$document.find('body').on('scroll', fireRecalculating);
    	}

    	// Declare the debounced function outside recalculating for
    	// proper debouncing
    	var debouncedRecalculate = $$debounce(function () {
    		// if popup is visible
    		if (scope.matches.length) {
    			recalculatePosition();
    		}

    		scope.moveInProgress = false;
    	}, eventDebounceTime);

    	// Default progress type
    	scope.moveInProgress = false;

    	function fireRecalculating() {
    		if (!scope.moveInProgress) {
    			scope.moveInProgress = true;
    			scope.$digest();
    		}

    		debouncedRecalculate();
    	}

    	// recalculate actual position and set new values to scope
    	// after digest loop is popup in right position
    	function recalculatePosition() {
    		scope.position = appendToBody ? $position.offset(element) : $position.position(element);
    		scope.position.top += element.prop('offsetHeight');
    	}

    	//we need to propagate user's query so we can higlight matches
    	scope.query = undefined;

    	//Declare the timeout promise var outside the function scope so that stacked calls can be cancelled later
    	var timeoutPromise;

    	var scheduleSearchWithTimeout = function (inputValue) {
    		timeoutPromise = $timeout(function () {
    			getMatchesAsync(inputValue);
    		}, waitTime);
    	};

    	var cancelPreviousTimeout = function () {
    		if (timeoutPromise) {
    			$timeout.cancel(timeoutPromise);
    		}
    	};

    	resetMatches();

    	scope.assignIsOpen = function (isOpen) {
    		isOpenSetter(originalScope, isOpen);
    	};

    	scope.select = function (activeIdx, evt) {
    		//called from within the $digest() cycle
    		var locals = {};
    		var model, item;

    		selected = true;
    		locals[parserResult.itemName] = item = scope.matches[activeIdx].model;
    		model = parserResult.modelMapper(originalScope, locals);
    		$setModelValue(originalScope, model);
    		modelCtrl.$setValidity('editable', true);
    		modelCtrl.$setValidity('parse', true);

    		onSelectCallback(originalScope, {
    			$item: item,
    			$model: model,
    			$label: parserResult.viewMapper(originalScope, locals),
    			$event: evt
    		});

    		resetMatches();

    		//return focus to the input element if a match was selected via a mouse click event
    		// use timeout to avoid $rootScope:inprog error
    		if (scope.$eval(attrs.typeaheadFocusOnSelect) !== false) {
    			$timeout(function () { element[0].focus(); }, 0, false);
    		}
    	};

    	//bind keyboard events: arrows up(38) / down(40), enter(13) and tab(9), esc(27)
    	element.on('keydown', function (evt) {
    		//typeahead is open and an "interesting" key was pressed
    		if (scope.matches.length === 0 || HOT_KEYS.indexOf(evt.which) === -1) {
    			return;
    		}

    		var shouldSelect = isSelectEvent(originalScope, { $event: evt });

    		/**
			 * if there's nothing selected (i.e. focusFirst) and enter or tab is hit
			 * or
			 * shift + tab is pressed to bring focus to the previous element
			 * then clear the results
			 */
    		if (scope.activeIdx === -1 && shouldSelect || evt.which === 9 && !!evt.shiftKey) {
    			resetMatches();
    			scope.$digest();
    			return;
    		}

    		evt.preventDefault();
    		var target;
    		switch (evt.which) {
    			case 27: // escape
    				evt.stopPropagation();

    				resetMatches();
    				originalScope.$digest();
    				break;
    			case 38: // up arrow
    				scope.activeIdx = (scope.activeIdx > 0 ? scope.activeIdx : scope.matches.length) - 1;
    				scope.$digest();
    				target = popUpEl[0].querySelectorAll('.uib-typeahead-match')[scope.activeIdx];
    				target.parentNode.scrollTop = target.offsetTop;
    				break;
    			case 40: // down arrow
    				scope.activeIdx = (scope.activeIdx + 1) % scope.matches.length;
    				scope.$digest();
    				target = popUpEl[0].querySelectorAll('.uib-typeahead-match')[scope.activeIdx];
    				target.parentNode.scrollTop = target.offsetTop;
    				break;
    			default:
    				if (shouldSelect) {
    					scope.$apply(function () {
    						if (angular.isNumber(scope.debounceUpdate) || angular.isObject(scope.debounceUpdate)) {
    							$$debounce(function () {
    								scope.select(scope.activeIdx, evt);
    							}, angular.isNumber(scope.debounceUpdate) ? scope.debounceUpdate : scope.debounceUpdate['default']);
    						} else {
    							scope.select(scope.activeIdx, evt);
    						}
    					});
    				}
    		}
    	});

    	element.bind('focus', function (evt) {
    		hasFocus = true;
    		if (minLength === 0 && !modelCtrl.$viewValue) {
    			$timeout(function () {
    				getMatchesAsync(modelCtrl.$viewValue, evt);
    			}, 0);
    		}
    	});

    	element.bind('blur', function (evt) {
    		if (isSelectOnBlur && scope.matches.length && scope.activeIdx !== -1 && !selected) {
    			selected = true;
    			scope.$apply(function () {
    				if (angular.isObject(scope.debounceUpdate) && angular.isNumber(scope.debounceUpdate.blur)) {
    					$$debounce(function () {
    						scope.select(scope.activeIdx, evt);
    					}, scope.debounceUpdate.blur);
    				} else {
    					scope.select(scope.activeIdx, evt);
    				}
    			});
    		}
    		if (!isEditable && modelCtrl.$error.editable) {
    			modelCtrl.$setViewValue();
    			scope.$apply(function () {
    				// Reset validity as we are clearing
    				modelCtrl.$setValidity('editable', true);
    				modelCtrl.$setValidity('parse', true);
    			});
    			element.val('');
    		}
    		hasFocus = false;
    		selected = false;
    	});

    	// Keep reference to click handler to unbind it.
    	var dismissClickHandler = function (evt) {
    		// Issue #3973
    		// Firefox treats right click as a click on document
    		if (element[0] !== evt.target && evt.which !== 3 && scope.matches.length !== 0) {
    			resetMatches();
    			if (!$rootScope.$$phase) {
    				originalScope.$digest();
    			}
    		}
    	};

    	$document.on('click', dismissClickHandler);

    	originalScope.$on('$destroy', function () {
    		$document.off('click', dismissClickHandler);
    		if (appendToBody || appendTo) {
    			$popup.remove();
    		}

    		if (appendToBody) {
    			angular.element($window).off('resize', fireRecalculating);
    			$document.find('body').off('scroll', fireRecalculating);
    		}
    		// Prevent jQuery cache memory leak
    		popUpEl.remove();

    		if (showHint) {
    			inputsContainer.remove();
    		}
    	});

    	var $popup = $compile(popUpEl)(scope);

    	if (appendToBody) {
    		$document.find('body').append($popup);
    	} else if (appendTo) {
    		angular.element(appendTo).eq(0).append($popup);
    	} else {
    		element.after($popup);
    	}

    	this.init = function (_modelCtrl, _ngModelOptions) {
    		modelCtrl = _modelCtrl;
    		ngModelOptions = _ngModelOptions;

    		scope.debounceUpdate = modelCtrl.$options && $parse(modelCtrl.$options.debounce)(originalScope);

    		//plug into $parsers pipeline to open a typeahead on view changes initiated from DOM
    		//$parsers kick-in on all the changes coming from the view as well as manually triggered by $setViewValue
    		modelCtrl.$parsers.unshift(function (inputValue) {
    			hasFocus = true;

    			if (minLength === 0 || inputValue && inputValue.length >= minLength) {
    				if (waitTime > 0) {
    					cancelPreviousTimeout();
    					scheduleSearchWithTimeout(inputValue);
    				} else {
    					getMatchesAsync(inputValue);
    				}
    			} else {
    				isLoadingSetter(originalScope, false);
    				cancelPreviousTimeout();
    				resetMatches();
    			}

    			if (isEditable) {
    				return inputValue;
    			}

    			if (!inputValue) {
    				// Reset in case user had typed something previously.
    				modelCtrl.$setValidity('editable', true);
    				return null;
    			}

    			modelCtrl.$setValidity('editable', false);
    			return undefined;
    		});

    		modelCtrl.$formatters.push(function (modelValue) {
    			var candidateViewValue, emptyViewValue;
    			var locals = {};

    			// The validity may be set to false via $parsers (see above) if
    			// the model is restricted to selected values. If the model
    			// is set manually it is considered to be valid.
    			if (!isEditable) {
    				modelCtrl.$setValidity('editable', true);
    			}

    			if (inputFormatter) {
    				locals.$model = modelValue;
    				return inputFormatter(originalScope, locals);
    			}

    			//it might happen that we don't have enough info to properly render input value
    			//we need to check for this situation and simply return model value if we can't apply custom formatting
    			locals[parserResult.itemName] = modelValue;
    			candidateViewValue = parserResult.viewMapper(originalScope, locals);
    			locals[parserResult.itemName] = undefined;
    			emptyViewValue = parserResult.viewMapper(originalScope, locals);

    			return candidateViewValue !== emptyViewValue ? candidateViewValue : modelValue;
    		});
    	};
    }])

  .directive('uibTypeahead', function () {
  	return {
  		controller: 'UibTypeaheadController',
  		require: ['ngModel', '^?ngModelOptions', 'uibTypeahead'],
  		link: function (originalScope, element, attrs, ctrls) {
  			ctrls[2].init(ctrls[0], ctrls[1]);
  		}
  	};
  })

  .directive('uibTypeaheadPopup', ['$$debounce', function ($$debounce) {
  	return {
  		scope: {
  			matches: '=',
  			query: '=',
  			active: '=',
  			position: '&',
  			moveInProgress: '=',
  			select: '&',
  			assignIsOpen: '&',
  			debounce: '&'
  		},
  		replace: true,
  		templateUrl: function (element, attrs) {
  			return attrs.popupTemplateUrl || 'uib/template/typeahead/typeahead-popup.html';
  		},
  		link: function (scope, element, attrs) {
  			scope.templateUrl = attrs.templateUrl;

  			scope.isOpen = function () {
  				var isDropdownOpen = scope.matches.length > 0;
  				scope.assignIsOpen({ isOpen: isDropdownOpen });
  				return isDropdownOpen;
  			};

  			scope.isActive = function (matchIdx) {
  				return scope.active === matchIdx;
  			};

  			scope.selectActive = function (matchIdx) {
  				scope.active = matchIdx;
  			};

  			scope.selectMatch = function (activeIdx, evt) {
  				var debounce = scope.debounce();
  				if (angular.isNumber(debounce) || angular.isObject(debounce)) {
  					$$debounce(function () {
  						scope.select({ activeIdx: activeIdx, evt: evt });
  					}, angular.isNumber(debounce) ? debounce : debounce['default']);
  				} else {
  					scope.select({ activeIdx: activeIdx, evt: evt });
  				}
  			};
  		}
  	};
  }])

  .directive('uibTypeaheadMatch', ['$templateRequest', '$compile', '$parse', function ($templateRequest, $compile, $parse) {
  	return {
  		scope: {
  			index: '=',
  			match: '=',
  			query: '='
  		},
  		link: function (scope, element, attrs) {
  			var tplUrl = $parse(attrs.templateUrl)(scope.$parent) || 'uib/template/typeahead/typeahead-match.html';
  			$templateRequest(tplUrl).then(function (tplContent) {
  				var tplEl = angular.element(tplContent.trim());
  				element.replaceWith(tplEl);
  				$compile(tplEl)(scope);
  			});
  		}
  	};
  }])

  .filter('uibTypeaheadHighlight', ['$sce', '$injector', '$log', function ($sce, $injector, $log) {
  	var isSanitizePresent;
  	isSanitizePresent = $injector.has('$sanitize');

  	function escapeRegexp(queryToEscape) {
  		// Regex: capture the whole query string and replace it with the string that will be used to match
  		// the results, for example if the capture is "a" the result will be \a
  		return queryToEscape.replace(/([.?*+^$[\]\\(){}|-])/g, '\\$1');
  	}

  	function containsHtml(matchItem) {
  		return /<.*>/g.test(matchItem);
  	}

  	return function (matchItem, query) {
  		if (!isSanitizePresent && containsHtml(matchItem)) {
  			$log.warn('Unsafe use of typeahead please use ngSanitize'); // Warn the user about the danger
  		}
  		matchItem = query ? ('' + matchItem).replace(new RegExp(escapeRegexp(query), 'gi'), '<strong>$&</strong>') : matchItem; // Replaces the capture string with a the same string inside of a "strong" tag
  		if (!isSanitizePresent) {
  			matchItem = $sce.trustAsHtml(matchItem); // If $sanitize is not present we pack the string in a $sce object for the ng-bind-html directive
  		}
  		return matchItem;
  	};
  }]);

angular.module("uib/template/accordion/accordion-group.html", []).run(["$templateCache", function ($templateCache) {
	$templateCache.put("uib/template/accordion/accordion-group.html",
	  "<div role=\"tab\" id=\"{{::headingId}}\" aria-selected=\"{{isOpen}}\" class=\"panel-heading\" ng-keypress=\"toggleOpen($event)\">\n" +
	  "  <h4 class=\"panel-title\">\n" +
	  "    <a role=\"button\" data-toggle=\"collapse\" href aria-expanded=\"{{isOpen}}\" aria-controls=\"{{::panelId}}\" tabindex=\"0\" class=\"accordion-toggle\" ng-click=\"toggleOpen()\" uib-accordion-transclude=\"heading\" ng-disabled=\"isDisabled\" uib-tabindex-toggle><span uib-accordion-header ng-class=\"{'text-muted': isDisabled}\">{{heading}}</span></a>\n" +
	  "  </h4>\n" +
	  "</div>\n" +
	  "<div id=\"{{::panelId}}\" aria-labelledby=\"{{::headingId}}\" aria-hidden=\"{{!isOpen}}\" role=\"tabpanel\" class=\"panel-collapse collapse\" uib-collapse=\"!isOpen\">\n" +
	  "  <div class=\"panel-body\" ng-transclude></div>\n" +
	  "</div>\n" +
	  "");
}]);

angular.module("uib/template/accordion/accordion.html", []).run(["$templateCache", function ($templateCache) {
	$templateCache.put("uib/template/accordion/accordion.html",
	  "<div role=\"tablist\" class=\"panel-group\" ng-transclude></div>");
}]);

angular.module("uib/template/alert/alert.html", []).run(["$templateCache", function ($templateCache) {
	$templateCache.put("uib/template/alert/alert.html",
	  "<button ng-show=\"closeable\" type=\"button\" class=\"close\" ng-click=\"close({$event: $event})\">\n" +
	  "  <span aria-hidden=\"true\">&times;</span>\n" +
	  "  <span class=\"sr-only\">Close</span>\n" +
	  "</button>\n" +
	  "<div ng-transclude></div>\n" +
	  "");
}]);

angular.module("uib/template/carousel/carousel.html", []).run(["$templateCache", function ($templateCache) {
	$templateCache.put("uib/template/carousel/carousel.html",
	  "<div class=\"carousel-inner\" ng-transclude></div>\n" +
	  "<a role=\"button\" href class=\"left carousel-control\" ng-click=\"prev()\" ng-class=\"{ disabled: isPrevDisabled() }\" ng-show=\"slides.length > 1\">\n" +
	  "  <span aria-hidden=\"true\" class=\"glyphicon glyphicon-chevron-left\"></span>\n" +
	  "  <span class=\"sr-only\">previous</span>\n" +
	  "</a>\n" +
	  "<a role=\"button\" href class=\"right carousel-control\" ng-click=\"next()\" ng-class=\"{ disabled: isNextDisabled() }\" ng-show=\"slides.length > 1\">\n" +
	  "  <span aria-hidden=\"true\" class=\"glyphicon glyphicon-chevron-right\"></span>\n" +
	  "  <span class=\"sr-only\">next</span>\n" +
	  "</a>\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)\">\n" +
	  "    <span class=\"sr-only\">slide {{ $index + 1 }} of {{ slides.length }}<span ng-if=\"isActive(slide)\">, currently active</span></span>\n" +
	  "  </li>\n" +
	  "</ol>\n" +
	  "");
}]);

angular.module("uib/template/carousel/slide.html", []).run(["$templateCache", function ($templateCache) {
	$templateCache.put("uib/template/carousel/slide.html",
	  "<div class=\"text-center\" ng-transclude></div>\n" +
	  "");
}]);

angular.module("uib/template/datepicker/datepicker.html", []).run(["$templateCache", function ($templateCache) {
	$templateCache.put("uib/template/datepicker/datepicker.html",
	  "<div ng-switch=\"datepickerMode\">\n" +
	  "  <div uib-daypicker ng-switch-when=\"day\" tabindex=\"0\" class=\"uib-daypicker\"></div>\n" +
	  "  <div uib-monthpicker ng-switch-when=\"month\" tabindex=\"0\" class=\"uib-monthpicker\"></div>\n" +
	  "  <div uib-yearpicker ng-switch-when=\"year\" tabindex=\"0\" class=\"uib-yearpicker\"></div>\n" +
	  "</div>\n" +
	  "");
}]);

angular.module("uib/template/datepicker/day.html", []).run(["$templateCache", function ($templateCache) {
	$templateCache.put("uib/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 uib-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 uib-title\" ng-click=\"toggleMode()\" ng-disabled=\"datepickerMode === maxMode\" tabindex=\"-1\"><strong>{{title}}</strong></button></th>\n" +
	  "      <th><button type=\"button\" class=\"btn btn-default btn-sm pull-right uib-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 class=\"uib-weeks\" ng-repeat=\"row in rows track by $index\" role=\"row\">\n" +
	  "      <td ng-if=\"showWeeks\" class=\"text-center h6\"><em>{{ weekNumbers[$index] }}</em></td>\n" +
	  "      <td ng-repeat=\"dt in row\" class=\"uib-day text-center\" role=\"gridcell\"\n" +
	  "        id=\"{{::dt.uid}}\"\n" +
	  "        ng-class=\"::dt.customClass\">\n" +
	  "        <button type=\"button\" class=\"btn btn-default btn-sm\"\n" +
	  "          uib-is-class=\"\n" +
	  "            'btn-info' for selectedDt,\n" +
	  "            'active' for activeDt\n" +
	  "            on dt\"\n" +
	  "          ng-click=\"select(dt.date)\"\n" +
	  "          ng-disabled=\"::dt.disabled\"\n" +
	  "          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("uib/template/datepicker/month.html", []).run(["$templateCache", function ($templateCache) {
	$templateCache.put("uib/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 uib-left\" ng-click=\"move(-1)\" tabindex=\"-1\"><i class=\"glyphicon glyphicon-chevron-left\"></i></button></th>\n" +
	  "      <th colspan=\"{{::yearHeaderColspan}}\"><button id=\"{{::uniqueId}}-title\" role=\"heading\" aria-live=\"assertive\" aria-atomic=\"true\" type=\"button\" class=\"btn btn-default btn-sm uib-title\" ng-click=\"toggleMode()\" ng-disabled=\"datepickerMode === maxMode\" tabindex=\"-1\"><strong>{{title}}</strong></button></th>\n" +
	  "      <th><button type=\"button\" class=\"btn btn-default btn-sm pull-right uib-right\" ng-click=\"move(1)\" tabindex=\"-1\"><i class=\"glyphicon glyphicon-chevron-right\"></i></button></th>\n" +
	  "    </tr>\n" +
	  "  </thead>\n" +
	  "  <tbody>\n" +
	  "    <tr class=\"uib-months\" ng-repeat=\"row in rows track by $index\" role=\"row\">\n" +
	  "      <td ng-repeat=\"dt in row\" class=\"uib-month text-center\" role=\"gridcell\"\n" +
	  "        id=\"{{::dt.uid}}\"\n" +
	  "        ng-class=\"::dt.customClass\">\n" +
	  "        <button type=\"button\" class=\"btn btn-default\"\n" +
	  "          uib-is-class=\"\n" +
	  "            'btn-info' for selectedDt,\n" +
	  "            'active' for activeDt\n" +
	  "            on dt\"\n" +
	  "          ng-click=\"select(dt.date)\"\n" +
	  "          ng-disabled=\"::dt.disabled\"\n" +
	  "          tabindex=\"-1\"><span ng-class=\"::{'text-info': dt.current}\">{{::dt.label}}</span></button>\n" +
	  "      </td>\n" +
	  "    </tr>\n" +
	  "  </tbody>\n" +
	  "</table>\n" +
	  "");
}]);

angular.module("uib/template/datepicker/year.html", []).run(["$templateCache", function ($templateCache) {
	$templateCache.put("uib/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 uib-left\" ng-click=\"move(-1)\" tabindex=\"-1\"><i class=\"glyphicon glyphicon-chevron-left\"></i></button></th>\n" +
	  "      <th colspan=\"{{::columns - 2}}\"><button id=\"{{::uniqueId}}-title\" role=\"heading\" aria-live=\"assertive\" aria-atomic=\"true\" type=\"button\" class=\"btn btn-default btn-sm uib-title\" ng-click=\"toggleMode()\" ng-disabled=\"datepickerMode === maxMode\" tabindex=\"-1\"><strong>{{title}}</strong></button></th>\n" +
	  "      <th><button type=\"button\" class=\"btn btn-default btn-sm pull-right uib-right\" ng-click=\"move(1)\" tabindex=\"-1\"><i class=\"glyphicon glyphicon-chevron-right\"></i></button></th>\n" +
	  "    </tr>\n" +
	  "  </thead>\n" +
	  "  <tbody>\n" +
	  "    <tr class=\"uib-years\" ng-repeat=\"row in rows track by $index\" role=\"row\">\n" +
	  "      <td ng-repeat=\"dt in row\" class=\"uib-year text-center\" role=\"gridcell\"\n" +
	  "        id=\"{{::dt.uid}}\"\n" +
	  "        ng-class=\"::dt.customClass\">\n" +
	  "        <button type=\"button\" class=\"btn btn-default\"\n" +
	  "          uib-is-class=\"\n" +
	  "            'btn-info' for selectedDt,\n" +
	  "            'active' for activeDt\n" +
	  "            on dt\"\n" +
	  "          ng-click=\"select(dt.date)\"\n" +
	  "          ng-disabled=\"::dt.disabled\"\n" +
	  "          tabindex=\"-1\"><span ng-class=\"::{'text-info': dt.current}\">{{::dt.label}}</span></button>\n" +
	  "      </td>\n" +
	  "    </tr>\n" +
	  "  </tbody>\n" +
	  "</table>\n" +
	  "");
}]);

angular.module("uib/template/datepickerPopup/popup.html", []).run(["$templateCache", function ($templateCache) {
	$templateCache.put("uib/template/datepickerPopup/popup.html",
	  "<ul class=\"uib-datepicker-popup dropdown-menu uib-position-measure\" dropdown-nested ng-if=\"isOpen\" ng-keydown=\"keydown($event)\" ng-click=\"$event.stopPropagation()\">\n" +
	  "  <li ng-transclude></li>\n" +
	  "  <li ng-if=\"showButtonBar\" class=\"uib-button-bar\">\n" +
	  "    <span class=\"btn-group pull-left\">\n" +
	  "      <button type=\"button\" class=\"btn btn-sm btn-info uib-datepicker-current\" ng-click=\"select('today', $event)\" ng-disabled=\"isDisabled('today')\">{{ getText('current') }}</button>\n" +
	  "      <button type=\"button\" class=\"btn btn-sm btn-danger uib-clear\" ng-click=\"select(null, $event)\">{{ getText('clear') }}</button>\n" +
	  "    </span>\n" +
	  "    <button type=\"button\" class=\"btn btn-sm btn-success pull-right uib-close\" ng-click=\"close($event)\">{{ getText('close') }}</button>\n" +
	  "  </li>\n" +
	  "</ul>\n" +
	  "");
}]);

angular.module("uib/template/modal/window.html", []).run(["$templateCache", function ($templateCache) {
	$templateCache.put("uib/template/modal/window.html",
	  "<div class=\"modal-dialog {{size ? 'modal-' + size : ''}}\"><div class=\"modal-content\" uib-modal-transclude></div></div>\n" +
	  "");
}]);

angular.module("uib/template/pager/pager.html", []).run(["$templateCache", function ($templateCache) {
	$templateCache.put("uib/template/pager/pager.html",
	  "<li ng-class=\"{disabled: noPrevious()||ngDisabled, previous: align}\"><a href ng-click=\"selectPage(page - 1, $event)\" ng-disabled=\"noPrevious()||ngDisabled\" uib-tabindex-toggle>{{::getText('previous')}}</a></li>\n" +
	  "<li ng-class=\"{disabled: noNext()||ngDisabled, next: align}\"><a href ng-click=\"selectPage(page + 1, $event)\" ng-disabled=\"noNext()||ngDisabled\" uib-tabindex-toggle>{{::getText('next')}}</a></li>\n" +
	  "");
}]);

angular.module("uib/template/pagination/pagination.html", []).run(["$templateCache", function ($templateCache) {
	$templateCache.put("uib/template/pagination/pagination.html",
	  "<li ng-if=\"::boundaryLinks\" ng-class=\"{disabled: noPrevious()||ngDisabled}\" class=\"pagination-first\"><a href ng-click=\"selectPage(1, $event)\" ng-disabled=\"noPrevious()||ngDisabled\" uib-tabindex-toggle>{{::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)\" ng-disabled=\"noPrevious()||ngDisabled\" uib-tabindex-toggle>{{::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)\" ng-disabled=\"ngDisabled&&!page.active\" uib-tabindex-toggle>{{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)\" ng-disabled=\"noNext()||ngDisabled\" uib-tabindex-toggle>{{::getText('next')}}</a></li>\n" +
	  "<li ng-if=\"::boundaryLinks\" ng-class=\"{disabled: noNext()||ngDisabled}\" class=\"pagination-last\"><a href ng-click=\"selectPage(totalPages, $event)\" ng-disabled=\"noNext()||ngDisabled\" uib-tabindex-toggle>{{::getText('last')}}</a></li>\n" +
	  "");
}]);

angular.module("uib/template/tooltip/tooltip-html-popup.html", []).run(["$templateCache", function ($templateCache) {
	$templateCache.put("uib/template/tooltip/tooltip-html-popup.html",
	  "<div class=\"tooltip-arrow\"></div>\n" +
	  "<div class=\"tooltip-inner\" ng-bind-html=\"contentExp()\"></div>\n" +
	  "");
}]);

angular.module("uib/template/tooltip/tooltip-popup.html", []).run(["$templateCache", function ($templateCache) {
	$templateCache.put("uib/template/tooltip/tooltip-popup.html",
	  "<div class=\"tooltip-arrow\"></div>\n" +
	  "<div class=\"tooltip-inner\" ng-bind=\"content\"></div>\n" +
	  "");
}]);

angular.module("uib/template/tooltip/tooltip-template-popup.html", []).run(["$templateCache", function ($templateCache) {
	$templateCache.put("uib/template/tooltip/tooltip-template-popup.html",
	  "<div class=\"tooltip-arrow\"></div>\n" +
	  "<div class=\"tooltip-inner\"\n" +
	  "  uib-tooltip-template-transclude=\"contentExp()\"\n" +
	  "  tooltip-template-transclude-scope=\"originScope()\"></div>\n" +
	  "");
}]);

angular.module("uib/template/popover/popover-html.html", []).run(["$templateCache", function ($templateCache) {
	$templateCache.put("uib/template/popover/popover-html.html",
	  "<div class=\"arrow\"></div>\n" +
	  "\n" +
	  "<div class=\"popover-inner\">\n" +
	  "    <h3 class=\"popover-title\" ng-bind=\"uibTitle\" ng-if=\"uibTitle\"></h3>\n" +
	  "    <div class=\"popover-content\" ng-bind-html=\"contentExp()\"></div>\n" +
	  "</div>\n" +
	  "");
}]);

angular.module("uib/template/popover/popover-template.html", []).run(["$templateCache", function ($templateCache) {
	$templateCache.put("uib/template/popover/popover-template.html",
	  "<div class=\"arrow\"></div>\n" +
	  "\n" +
	  "<div class=\"popover-inner\">\n" +
	  "    <h3 class=\"popover-title\" ng-bind=\"uibTitle\" ng-if=\"uibTitle\"></h3>\n" +
	  "    <div class=\"popover-content\"\n" +
	  "      uib-tooltip-template-transclude=\"contentExp()\"\n" +
	  "      tooltip-template-transclude-scope=\"originScope()\"></div>\n" +
	  "</div>\n" +
	  "");
}]);

angular.module("uib/template/popover/popover.html", []).run(["$templateCache", function ($templateCache) {
	$templateCache.put("uib/template/popover/popover.html",
	  "<div class=\"arrow\"></div>\n" +
	  "\n" +
	  "<div class=\"popover-inner\">\n" +
	  "    <h3 class=\"popover-title\" ng-bind=\"uibTitle\" ng-if=\"uibTitle\"></h3>\n" +
	  "    <div class=\"popover-content\" ng-bind=\"content\"></div>\n" +
	  "</div>\n" +
	  "");
}]);

angular.module("uib/template/progressbar/bar.html", []).run(["$templateCache", function ($templateCache) {
	$templateCache.put("uib/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}}%\" aria-labelledby=\"{{::title}}\" ng-transclude></div>\n" +
	  "");
}]);

angular.module("uib/template/progressbar/progress.html", []).run(["$templateCache", function ($templateCache) {
	$templateCache.put("uib/template/progressbar/progress.html",
	  "<div class=\"progress\" ng-transclude aria-labelledby=\"{{::title}}\"></div>");
}]);

angular.module("uib/template/progressbar/progressbar.html", []).run(["$templateCache", function ($templateCache) {
	$templateCache.put("uib/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}}%\" aria-labelledby=\"{{::title}}\" ng-transclude></div>\n" +
	  "</div>\n" +
	  "");
}]);

angular.module("uib/template/rating/rating.html", []).run(["$templateCache", function ($templateCache) {
	$templateCache.put("uib/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}}\" aria-valuetext=\"{{title}}\">\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("uib/template/tabs/tab.html", []).run(["$templateCache", function ($templateCache) {
	$templateCache.put("uib/template/tabs/tab.html",
	  "<li ng-class=\"[{active: active, disabled: disabled}, classes]\" class=\"uib-tab nav-item\">\n" +
	  "  <a href ng-click=\"select($event)\" class=\"nav-link\" uib-tab-heading-transclude>{{heading}}</a>\n" +
	  "</li>\n" +
	  "");
}]);

angular.module("uib/template/tabs/tabset.html", []).run(["$templateCache", function ($templateCache) {
	$templateCache.put("uib/template/tabs/tabset.html",
	  "<div>\n" +
	  "  <ul class=\"nav nav-{{tabset.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 tabset.tabs\"\n" +
	  "         ng-class=\"{active: tabset.active === tab.index}\"\n" +
	  "         uib-tab-content-transclude=\"tab\">\n" +
	  "    </div>\n" +
	  "  </div>\n" +
	  "</div>\n" +
	  "");
}]);

angular.module("uib/template/timepicker/timepicker.html", []).run(["$templateCache", function ($templateCache) {
	$templateCache.put("uib/template/timepicker/timepicker.html",
	  "<table class=\"uib-timepicker\">\n" +
	  "  <tbody>\n" +
	  "    <tr class=\"text-center\" ng-show=\"::showSpinners\">\n" +
	  "      <td class=\"uib-increment hours\"><a ng-click=\"incrementHours()\" ng-class=\"{disabled: noIncrementHours()}\" class=\"btn btn-link\" ng-disabled=\"noIncrementHours()\" tabindex=\"-1\"><span class=\"glyphicon glyphicon-chevron-up\"></span></a></td>\n" +
	  "      <td>&nbsp;</td>\n" +
	  "      <td class=\"uib-increment minutes\"><a ng-click=\"incrementMinutes()\" ng-class=\"{disabled: noIncrementMinutes()}\" class=\"btn btn-link\" ng-disabled=\"noIncrementMinutes()\" tabindex=\"-1\"><span class=\"glyphicon glyphicon-chevron-up\"></span></a></td>\n" +
	  "      <td ng-show=\"showSeconds\">&nbsp;</td>\n" +
	  "      <td ng-show=\"showSeconds\" class=\"uib-increment seconds\"><a ng-click=\"incrementSeconds()\" ng-class=\"{disabled: noIncrementSeconds()}\" class=\"btn btn-link\" ng-disabled=\"noIncrementSeconds()\" tabindex=\"-1\"><span class=\"glyphicon glyphicon-chevron-up\"></span></a></td>\n" +
	  "      <td ng-show=\"showMeridian\"></td>\n" +
	  "    </tr>\n" +
	  "    <tr>\n" +
	  "      <td class=\"form-group uib-time hours\" ng-class=\"{'has-error': invalidHours}\">\n" +
	  "        <input type=\"text\" placeholder=\"HH\" ng-model=\"hours\" ng-change=\"updateHours()\" class=\"form-control text-center\" ng-readonly=\"::readonlyInput\" maxlength=\"2\" tabindex=\"{{::tabindex}}\" ng-disabled=\"noIncrementHours()\" ng-blur=\"blur()\">\n" +
	  "      </td>\n" +
	  "      <td class=\"uib-separator\">:</td>\n" +
	  "      <td class=\"form-group uib-time minutes\" ng-class=\"{'has-error': invalidMinutes}\">\n" +
	  "        <input type=\"text\" placeholder=\"MM\" ng-model=\"minutes\" ng-change=\"updateMinutes()\" class=\"form-control text-center\" ng-readonly=\"::readonlyInput\" maxlength=\"2\" tabindex=\"{{::tabindex}}\" ng-disabled=\"noIncrementMinutes()\" ng-blur=\"blur()\">\n" +
	  "      </td>\n" +
	  "      <td ng-show=\"showSeconds\" class=\"uib-separator\">:</td>\n" +
	  "      <td class=\"form-group uib-time seconds\" ng-class=\"{'has-error': invalidSeconds}\" ng-show=\"showSeconds\">\n" +
	  "        <input type=\"text\" placeholder=\"SS\" ng-model=\"seconds\" ng-change=\"updateSeconds()\" class=\"form-control text-center\" ng-readonly=\"readonlyInput\" maxlength=\"2\" tabindex=\"{{::tabindex}}\" ng-disabled=\"noIncrementSeconds()\" ng-blur=\"blur()\">\n" +
	  "      </td>\n" +
	  "      <td ng-show=\"showMeridian\" class=\"uib-time am-pm\"><button type=\"button\" ng-class=\"{disabled: noToggleMeridian()}\" class=\"btn btn-default text-center\" ng-click=\"toggleMeridian()\" ng-disabled=\"noToggleMeridian()\" tabindex=\"{{::tabindex}}\">{{meridian}}</button></td>\n" +
	  "    </tr>\n" +
	  "    <tr class=\"text-center\" ng-show=\"::showSpinners\">\n" +
	  "      <td class=\"uib-decrement hours\"><a ng-click=\"decrementHours()\" ng-class=\"{disabled: noDecrementHours()}\" class=\"btn btn-link\" ng-disabled=\"noDecrementHours()\" tabindex=\"-1\"><span class=\"glyphicon glyphicon-chevron-down\"></span></a></td>\n" +
	  "      <td>&nbsp;</td>\n" +
	  "      <td class=\"uib-decrement minutes\"><a ng-click=\"decrementMinutes()\" ng-class=\"{disabled: noDecrementMinutes()}\" class=\"btn btn-link\" ng-disabled=\"noDecrementMinutes()\" tabindex=\"-1\"><span class=\"glyphicon glyphicon-chevron-down\"></span></a></td>\n" +
	  "      <td ng-show=\"showSeconds\">&nbsp;</td>\n" +
	  "      <td ng-show=\"showSeconds\" class=\"uib-decrement seconds\"><a ng-click=\"decrementSeconds()\" ng-class=\"{disabled: noDecrementSeconds()}\" class=\"btn btn-link\" ng-disabled=\"noDecrementSeconds()\" tabindex=\"-1\"><span class=\"glyphicon glyphicon-chevron-down\"></span></a></td>\n" +
	  "      <td ng-show=\"showMeridian\"></td>\n" +
	  "    </tr>\n" +
	  "  </tbody>\n" +
	  "</table>\n" +
	  "");
}]);

angular.module("uib/template/typeahead/typeahead-match.html", []).run(["$templateCache", function ($templateCache) {
	$templateCache.put("uib/template/typeahead/typeahead-match.html",
	  "<a href\n" +
	  "   tabindex=\"-1\"\n" +
	  "   ng-bind-html=\"match.label | uibTypeaheadHighlight:query\"\n" +
	  "   ng-attr-title=\"{{match.label}}\"></a>\n" +
	  "");
}]);

angular.module("uib/template/typeahead/typeahead-popup.html", []).run(["$templateCache", function ($templateCache) {
	$templateCache.put("uib/template/typeahead/typeahead-popup.html",
	  "<ul class=\"dropdown-menu\" ng-show=\"isOpen() && !moveInProgress\" ng-style=\"{top: position().top+'px', left: position().left+'px'}\" role=\"listbox\" aria-hidden=\"{{!isOpen()}}\">\n" +
	  "    <li class=\"uib-typeahead-match\" ng-repeat=\"match in matches track by $index\" ng-class=\"{active: isActive($index) }\" ng-mouseenter=\"selectActive($index)\" ng-click=\"selectMatch($index, $event)\" role=\"option\" id=\"{{::match.id}}\">\n" +
	  "        <div uib-typeahead-match index=\"$index\" match=\"match\" query=\"query\" template-url=\"templateUrl\"></div>\n" +
	  "    </li>\n" +
	  "</ul>\n" +
	  "");
}]);
angular.module('ui.bootstrap.carousel').run(function () { !angular.$$csp().noInlineStyle && !angular.$$uibCarouselCss && 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>'); angular.$$uibCarouselCss = true; });
angular.module('ui.bootstrap.datepicker').run(function () { !angular.$$csp().noInlineStyle && !angular.$$uibDatepickerCss && angular.element(document).find('head').prepend('<style type="text/css">.uib-datepicker .uib-title{width:100%;}.uib-day button,.uib-month button,.uib-year button{min-width:100%;}.uib-left,.uib-right{width:100%}</style>'); angular.$$uibDatepickerCss = true; });
angular.module('ui.bootstrap.position').run(function () { !angular.$$csp().noInlineStyle && !angular.$$uibPositionCss && angular.element(document).find('head').prepend('<style type="text/css">.uib-position-measure{display:block !important;visibility:hidden !important;position:absolute !important;top:-9999px !important;left:-9999px !important;}.uib-position-scrollbar-measure{position:absolute !important;top:-9999px !important;width:50px !important;height:50px !important;overflow:scroll !important;}.uib-position-body-scrollbar-measure{overflow:scroll !important;}</style>'); angular.$$uibPositionCss = true; });
angular.module('ui.bootstrap.datepickerPopup').run(function () { !angular.$$csp().noInlineStyle && !angular.$$uibDatepickerpopupCss && angular.element(document).find('head').prepend('<style type="text/css">.uib-datepicker-popup.dropdown-menu{display:block;float:none;margin:0;}.uib-button-bar{padding:10px 9px 2px;}</style>'); angular.$$uibDatepickerpopupCss = true; });
angular.module('ui.bootstrap.tooltip').run(function () { !angular.$$csp().noInlineStyle && !angular.$$uibTooltipCss && angular.element(document).find('head').prepend('<style type="text/css">[uib-tooltip-popup].tooltip.top-left > .tooltip-arrow,[uib-tooltip-popup].tooltip.top-right > .tooltip-arrow,[uib-tooltip-popup].tooltip.bottom-left > .tooltip-arrow,[uib-tooltip-popup].tooltip.bottom-right > .tooltip-arrow,[uib-tooltip-popup].tooltip.left-top > .tooltip-arrow,[uib-tooltip-popup].tooltip.left-bottom > .tooltip-arrow,[uib-tooltip-popup].tooltip.right-top > .tooltip-arrow,[uib-tooltip-popup].tooltip.right-bottom > .tooltip-arrow,[uib-tooltip-html-popup].tooltip.top-left > .tooltip-arrow,[uib-tooltip-html-popup].tooltip.top-right > .tooltip-arrow,[uib-tooltip-html-popup].tooltip.bottom-left > .tooltip-arrow,[uib-tooltip-html-popup].tooltip.bottom-right > .tooltip-arrow,[uib-tooltip-html-popup].tooltip.left-top > .tooltip-arrow,[uib-tooltip-html-popup].tooltip.left-bottom > .tooltip-arrow,[uib-tooltip-html-popup].tooltip.right-top > .tooltip-arrow,[uib-tooltip-html-popup].tooltip.right-bottom > .tooltip-arrow,[uib-tooltip-template-popup].tooltip.top-left > .tooltip-arrow,[uib-tooltip-template-popup].tooltip.top-right > .tooltip-arrow,[uib-tooltip-template-popup].tooltip.bottom-left > .tooltip-arrow,[uib-tooltip-template-popup].tooltip.bottom-right > .tooltip-arrow,[uib-tooltip-template-popup].tooltip.left-top > .tooltip-arrow,[uib-tooltip-template-popup].tooltip.left-bottom > .tooltip-arrow,[uib-tooltip-template-popup].tooltip.right-top > .tooltip-arrow,[uib-tooltip-template-popup].tooltip.right-bottom > .tooltip-arrow,[uib-popover-popup].popover.top-left > .arrow,[uib-popover-popup].popover.top-right > .arrow,[uib-popover-popup].popover.bottom-left > .arrow,[uib-popover-popup].popover.bottom-right > .arrow,[uib-popover-popup].popover.left-top > .arrow,[uib-popover-popup].popover.left-bottom > .arrow,[uib-popover-popup].popover.right-top > .arrow,[uib-popover-popup].popover.right-bottom > .arrow,[uib-popover-html-popup].popover.top-left > .arrow,[uib-popover-html-popup].popover.top-right > .arrow,[uib-popover-html-popup].popover.bottom-left > .arrow,[uib-popover-html-popup].popover.bottom-right > .arrow,[uib-popover-html-popup].popover.left-top > .arrow,[uib-popover-html-popup].popover.left-bottom > .arrow,[uib-popover-html-popup].popover.right-top > .arrow,[uib-popover-html-popup].popover.right-bottom > .arrow,[uib-popover-template-popup].popover.top-left > .arrow,[uib-popover-template-popup].popover.top-right > .arrow,[uib-popover-template-popup].popover.bottom-left > .arrow,[uib-popover-template-popup].popover.bottom-right > .arrow,[uib-popover-template-popup].popover.left-top > .arrow,[uib-popover-template-popup].popover.left-bottom > .arrow,[uib-popover-template-popup].popover.right-top > .arrow,[uib-popover-template-popup].popover.right-bottom > .arrow{top:auto;bottom:auto;left:auto;right:auto;margin:0;}[uib-popover-popup].popover,[uib-popover-html-popup].popover,[uib-popover-template-popup].popover{display:block !important;}</style>'); angular.$$uibTooltipCss = true; });
angular.module('ui.bootstrap.timepicker').run(function () { !angular.$$csp().noInlineStyle && !angular.$$uibTimepickerCss && angular.element(document).find('head').prepend('<style type="text/css">.uib-time input{width:50px;}</style>'); angular.$$uibTimepickerCss = true; });
angular.module('ui.bootstrap.typeahead').run(function () { !angular.$$csp().noInlineStyle && !angular.$$uibTypeaheadCss && angular.element(document).find('head').prepend('<style type="text/css">[uib-typeahead-popup].dropdown-menu{display:block;}</style>'); angular.$$uibTypeaheadCss = true; });
/**
 * Bunch of useful filters for angularJS(with no external dependencies!)
 * @version v0.5.11 - 2016-08-16 * @link https://github.com/a8m/angular-filter
 * @author Ariel Mashraki <ariel@mashraki.co.il>
 * @license MIT License, http://www.opensource.org/licenses/MIT
 */!function(a,b,c){"use strict";function d(a){return D(a)?a:Object.keys(a).map(function(b){return a[b]})}function e(a){return null===a}function f(a,b){var d=Object.keys(a);return d.map(function(d){return b[d]!==c&&b[d]==a[d]}).indexOf(!1)==-1}function g(a,b){function c(a,b,c){for(var d=0;b+d<=a.length;){if(a.charAt(b+d)==c)return d;d++}return-1}for(var d=0,e=0;e<=b.length;e++){var f=c(a,d,b.charAt(e));if(f==-1)return!1;d+=f+1}return!0}function h(a,b,c){var d=0;return a.filter(function(a){var e=x(c)?d<b&&c(a):d<b;return d=e?d+1:d,e})}function i(a,b,c){return c.round(a*c.pow(10,b))/c.pow(10,b)}function j(a,b,c){b=b||[];var d=Object.keys(a);return d.forEach(function(d){if(C(a[d])&&!D(a[d])){var e=c?c+"."+d:c;j(a[d],b,e||d)}else{var f=c?c+"."+d:d;b.push(f)}}),b}function k(a){return a&&a.$evalAsync&&a.$watch}function l(){return function(a,b){return a>b}}function m(){return function(a,b){return a>=b}}function n(){return function(a,b){return a<b}}function o(){return function(a,b){return a<=b}}function p(){return function(a,b){return a==b}}function q(){return function(a,b){return a!=b}}function r(){return function(a,b){return a===b}}function s(){return function(a,b){return a!==b}}function t(a){return function(b,c){return b=C(b)?d(b):b,!(!D(b)||y(c))&&b.some(function(b){return A(c)&&C(b)||z(c)?a(c)(b):b===c})}}function u(a,b){return b=b||0,b>=a.length?a:D(a[b])?u(a.slice(0,b).concat(a[b],a.slice(b+1)),b):u(a,b+1)}function v(a){return function(b,c){function e(a,b){return!y(b)&&a.some(function(a){return H(a,b)})}if(b=C(b)?d(b):b,!D(b))return b;var f=[],g=a(c);return y(c)?b.filter(function(a,b,c){return c.indexOf(a)===b}):b.filter(function(a){var b=g(a);return!e(f,b)&&(f.push(b),!0)})}}function w(a,b,c){return b?a+c+w(a,--b,c):a}var x=b.isDefined,y=b.isUndefined,z=b.isFunction,A=b.isString,B=b.isNumber,C=b.isObject,D=b.isArray,E=b.forEach,F=b.extend,G=b.copy,H=b.equals;String.prototype.contains||(String.prototype.contains=function(){return String.prototype.indexOf.apply(this,arguments)!==-1}),b.module("a8m.angular",[]).filter("isUndefined",function(){return function(a){return b.isUndefined(a)}}).filter("isDefined",function(){return function(a){return b.isDefined(a)}}).filter("isFunction",function(){return function(a){return b.isFunction(a)}}).filter("isString",function(){return function(a){return b.isString(a)}}).filter("isNumber",function(){return function(a){return b.isNumber(a)}}).filter("isArray",function(){return function(a){return b.isArray(a)}}).filter("isObject",function(){return function(a){return b.isObject(a)}}).filter("isEqual",function(){return function(a,c){return b.equals(a,c)}}),b.module("a8m.conditions",[]).filter({isGreaterThan:l,">":l,isGreaterThanOrEqualTo:m,">=":m,isLessThan:n,"<":n,isLessThanOrEqualTo:o,"<=":o,isEqualTo:p,"==":p,isNotEqualTo:q,"!=":q,isIdenticalTo:r,"===":r,isNotIdenticalTo:s,"!==":s}),b.module("a8m.is-null",[]).filter("isNull",function(){return function(a){return e(a)}}),b.module("a8m.after-where",[]).filter("afterWhere",function(){return function(a,b){if(a=C(a)?d(a):a,!D(a)||y(b))return a;var c=a.map(function(a){return f(b,a)}).indexOf(!0);return a.slice(c===-1?0:c)}}),b.module("a8m.after",[]).filter("after",function(){return function(a,b){return a=C(a)?d(a):a,D(a)?a.slice(b):a}}),b.module("a8m.before-where",[]).filter("beforeWhere",function(){return function(a,b){if(a=C(a)?d(a):a,!D(a)||y(b))return a;var c=a.map(function(a){return f(b,a)}).indexOf(!0);return a.slice(0,c===-1?a.length:++c)}}),b.module("a8m.before",[]).filter("before",function(){return function(a,b){return a=C(a)?d(a):a,D(a)?a.slice(0,b?--b:b):a}}),b.module("a8m.chunk-by",["a8m.filter-watcher"]).filter("chunkBy",["filterWatcher",function(a){return function(b,c,d){function e(a,b){for(var c=[];a--;)c[a]=b;return c}function f(a,b,c){return D(a)?a.map(function(a,d,f){return d*=b,a=f.slice(d,d+b),!y(c)&&a.length<b?a.concat(e(b-a.length,c)):a}).slice(0,Math.ceil(a.length/b)):a}return a.isMemoized("chunkBy",arguments)||a.memoize("chunkBy",arguments,this,f(b,c,d))}}]),b.module("a8m.concat",[]).filter("concat",[function(){return function(a,b){if(y(b))return a;if(D(a))return C(b)?a.concat(d(b)):a.concat(b);if(C(a)){var c=d(a);return C(b)?c.concat(d(b)):c.concat(b)}return a}}]),b.module("a8m.contains",[]).filter({contains:["$parse",t],some:["$parse",t]}),b.module("a8m.count-by",[]).filter("countBy",["$parse",function(a){return function(b,c){var e,f={},g=a(c);return b=C(b)?d(b):b,!D(b)||y(c)?b:(b.forEach(function(a){e=g(a),f[e]||(f[e]=0),f[e]++}),f)}}]),b.module("a8m.defaults",[]).filter("defaults",["$parse",function(a){return function(b,c){if(b=C(b)?d(b):b,!D(b)||!C(c))return b;var e=j(c);return b.forEach(function(b){e.forEach(function(d){var e=a(d),f=e.assign;y(e(b))&&f(b,e(c))})}),b}}]),b.module("a8m.every",[]).filter("every",["$parse",function(a){return function(b,c){return b=C(b)?d(b):b,!(D(b)&&!y(c))||b.every(function(b){return C(b)||z(c)?a(c)(b):b===c})}}]),b.module("a8m.filter-by",[]).filter("filterBy",["$parse",function(a){return function(b,e,f,g){var h;return f=A(f)||B(f)?String(f).toLowerCase():c,b=C(b)?d(b):b,!D(b)||y(f)?b:b.filter(function(b){return e.some(function(c){if(~c.indexOf("+")){var d=c.replace(/\s+/g,"").split("+");h=d.map(function(c){return a(c)(b)}).join(" ")}else h=a(c)(b);return!(!A(h)&&!B(h))&&(h=String(h).toLowerCase(),g?h===f:h.contains(f))})})}}]),b.module("a8m.first",[]).filter("first",["$parse",function(a){return function(b){var e,f,g;return b=C(b)?d(b):b,D(b)?(g=Array.prototype.slice.call(arguments,1),e=B(g[0])?g[0]:1,f=B(g[0])?B(g[1])?c:g[1]:g[0],g.length?h(b,e,f?a(f):f):b[0]):b}}]),b.module("a8m.flatten",[]).filter("flatten",function(){return function(a,b){return b=b||!1,a=C(a)?d(a):a,D(a)?b?[].concat.apply([],a):u(a,0):a}}),b.module("a8m.fuzzy-by",[]).filter("fuzzyBy",["$parse",function(a){return function(b,c,e,f){var h,i,j=f||!1;return b=C(b)?d(b):b,!D(b)||y(c)||y(e)?b:(i=a(c),b.filter(function(a){return h=i(a),!!A(h)&&(h=j?h:h.toLowerCase(),e=j?e:e.toLowerCase(),g(h,e)!==!1)}))}}]),b.module("a8m.fuzzy",[]).filter("fuzzy",function(){return function(a,b,c){function e(a,b){var c,d,e=Object.keys(a);return 0<e.filter(function(e){return c=a[e],!!d||!!A(c)&&(c=f?c:c.toLowerCase(),d=g(c,b)!==!1)}).length}var f=c||!1;return a=C(a)?d(a):a,!D(a)||y(b)?a:(b=f?b:b.toLowerCase(),a.filter(function(a){return A(a)?(a=f?a:a.toLowerCase(),g(a,b)!==!1):!!C(a)&&e(a,b)}))}}),b.module("a8m.group-by",["a8m.filter-watcher"]).filter("groupBy",["$parse","filterWatcher",function(a,b){return function(c,d){function e(a,b){var c,d={};return E(a,function(a){c=b(a),d[c]||(d[c]=[]),d[c].push(a)}),d}return!C(c)||y(d)?c:b.isMemoized("groupBy",arguments)||b.memoize("groupBy",arguments,this,e(c,a(d)))}}]),b.module("a8m.is-empty",[]).filter("isEmpty",function(){return function(a){return C(a)?!d(a).length:!a.length}}),b.module("a8m.join",[]).filter("join",function(){return function(a,b){return y(a)||!D(a)?a:(y(b)&&(b=" "),a.join(b))}}),b.module("a8m.last",[]).filter("last",["$parse",function(a){return function(b){var e,f,g,i=G(b);return i=C(i)?d(i):i,D(i)?(g=Array.prototype.slice.call(arguments,1),e=B(g[0])?g[0]:1,f=B(g[0])?B(g[1])?c:g[1]:g[0],g.length?h(i.reverse(),e,f?a(f):f).reverse():i[i.length-1]):i}}]),b.module("a8m.map",[]).filter("map",["$parse",function(a){return function(b,c){return b=C(b)?d(b):b,!D(b)||y(c)?b:b.map(function(b){return a(c)(b)})}}]),b.module("a8m.omit",[]).filter("omit",["$parse",function(a){return function(b,c){return b=C(b)?d(b):b,!D(b)||y(c)?b:b.filter(function(b){return!a(c)(b)})}}]),b.module("a8m.pick",[]).filter("pick",["$parse",function(a){return function(b,c){return b=C(b)?d(b):b,!D(b)||y(c)?b:b.filter(function(b){return a(c)(b)})}}]),b.module("a8m.range",[]).filter("range",function(){return function(a,b,c,d,e){c=c||0,d=d||1;for(var f=0;f<parseInt(b);f++){var g=c+f*d;a.push(z(e)?e(g):g)}return a}}),b.module("a8m.remove-with",[]).filter("removeWith",function(){return function(a,b){return y(b)?a:(a=C(a)?d(a):a,a.filter(function(a){return!f(b,a)}))}}),b.module("a8m.remove",[]).filter("remove",function(){return function(a){a=C(a)?d(a):a;var b=Array.prototype.slice.call(arguments,1);return D(a)?a.filter(function(a){return!b.some(function(b){return H(b,a)})}):a}}),b.module("a8m.reverse",[]).filter("reverse",[function(){return function(a){return a=C(a)?d(a):a,A(a)?a.split("").reverse().join(""):D(a)?a.slice().reverse():a}}]),b.module("a8m.search-field",[]).filter("searchField",["$parse",function(a){return function(b){var c,e;b=C(b)?d(b):b;var f=Array.prototype.slice.call(arguments,1);return D(b)&&f.length?b.map(function(b){return e=f.map(function(d){return(c=a(d))(b)}).join(" "),F(b,{searchField:e})}):b}}]),b.module("a8m.to-array",[]).filter("toArray",function(){return function(a,b){return C(a)?b?Object.keys(a).map(function(b){return F(a[b],{$key:b})}):d(a):a}}),b.module("a8m.unique",[]).filter({unique:["$parse",v],uniq:["$parse",v]}),b.module("a8m.where",[]).filter("where",function(){return function(a,b){return y(b)?a:(a=C(a)?d(a):a,a.filter(function(a){return f(b,a)}))}}),b.module("a8m.xor",[]).filter("xor",["$parse",function(a){return function(b,c,e){function f(b,c){var d=a(e);return c.some(function(a){return e?H(d(a),d(b)):H(a,b)})}return e=e||!1,b=C(b)?d(b):b,c=C(c)?d(c):c,D(b)&&D(c)?b.concat(c).filter(function(a){return!(f(a,b)&&f(a,c))}):b}}]),b.module("a8m.math.byteFmt",["a8m.math"]).filter("byteFmt",["$math",function(a){return function(b,c){return B(c)&&isFinite(c)&&c%1===0&&c>=0&&B(b)&&isFinite(b)?b<1024?i(b,c,a)+" B":b<1048576?i(b/1024,c,a)+" KB":b<1073741824?i(b/1048576,c,a)+" MB":i(b/1073741824,c,a)+" GB":"NaN"}}]),b.module("a8m.math.degrees",["a8m.math"]).filter("degrees",["$math",function(a){return function(b,c){if(B(c)&&isFinite(c)&&c%1===0&&c>=0&&B(b)&&isFinite(b)){var d=180*b/a.PI;return a.round(d*a.pow(10,c))/a.pow(10,c)}return"NaN"}}]),b.module("a8m.math.kbFmt",["a8m.math"]).filter("kbFmt",["$math",function(a){return function(b,c){return B(c)&&isFinite(c)&&c%1===0&&c>=0&&B(b)&&isFinite(b)?b<1024?i(b,c,a)+" KB":b<1048576?i(b/1024,c,a)+" MB":i(b/1048576,c,a)+" GB":"NaN"}}]),b.module("a8m.math",[]).factory("$math",["$window",function(a){return a.Math}]),b.module("a8m.math.max",["a8m.math"]).filter("max",["$math","$parse",function(a,b){function c(c,d){var e=c.map(function(a){return b(d)(a)});return e.indexOf(a.max.apply(a,e))}return function(b,d){return D(b)?y(d)?a.max.apply(a,b):b[c(b,d)]:b}}]),b.module("a8m.math.min",["a8m.math"]).filter("min",["$math","$parse",function(a,b){function c(c,d){var e=c.map(function(a){return b(d)(a)});return e.indexOf(a.min.apply(a,e))}return function(b,d){return D(b)?y(d)?a.min.apply(a,b):b[c(b,d)]:b}}]),b.module("a8m.math.percent",["a8m.math"]).filter("percent",["$math","$window",function(a,b){return function(c,d,e){var f=A(c)?b.Number(c):c;return d=d||100,e=e||!1,!B(f)||b.isNaN(f)?c:e?a.round(f/d*100):f/d*100}}]),b.module("a8m.math.radians",["a8m.math"]).filter("radians",["$math",function(a){return function(b,c){if(B(c)&&isFinite(c)&&c%1===0&&c>=0&&B(b)&&isFinite(b)){var d=3.14159265359*b/180;return a.round(d*a.pow(10,c))/a.pow(10,c)}return"NaN"}}]),b.module("a8m.math.radix",[]).filter("radix",function(){return function(a,b){var c=/^[2-9]$|^[1-2]\d$|^3[0-6]$/;return B(a)&&c.test(b)?a.toString(b).toUpperCase():a}}),b.module("a8m.math.shortFmt",["a8m.math"]).filter("shortFmt",["$math",function(a){return function(b,c){return B(c)&&isFinite(c)&&c%1===0&&c>=0&&B(b)&&isFinite(b)?b<1e3?b:b<1e6?i(b/1e3,c,a)+" K":b<1e9?i(b/1e6,c,a)+" M":i(b/1e9,c,a)+" B":"NaN"}}]),b.module("a8m.math.sum",[]).filter("sum",function(){return function(a,b){return D(a)?a.reduce(function(a,b){return a+b},b||0):a}}),b.module("a8m.ends-with",[]).filter("endsWith",function(){return function(a,b,c){var d,e=c||!1;return!A(a)||y(b)?a:(a=e?a:a.toLowerCase(),d=a.length-b.length,a.indexOf(e?b:b.toLowerCase(),d)!==-1)}}),b.module("a8m.latinize",[]).filter("latinize",[function(){function a(a){return a.replace(/[^\u0000-\u007E]/g,function(a){return c[a]||a})}for(var b=[{base:"A",letters:"AⒶＡÀÁÂẦẤẪẨÃĀĂẰẮẴẲȦǠÄǞẢÅǺǍȀȂẠẬẶḀĄȺⱯ"},{base:"AA",letters:"Ꜳ"},{base:"AE",letters:"ÆǼǢ"},{base:"AO",letters:"Ꜵ"},{base:"AU",letters:"Ꜷ"},{base:"AV",letters:"ꜸꜺ"},{base:"AY",letters:"Ꜽ"},{base:"B",letters:"BⒷＢḂḄḆɃƂƁ"},{base:"C",letters:"CⒸＣĆĈĊČÇḈƇȻꜾ"},{base:"D",letters:"DⒹＤḊĎḌḐḒḎĐƋƊƉꝹ"},{base:"DZ",letters:"ǱǄ"},{base:"Dz",letters:"ǲǅ"},{base:"E",letters:"EⒺＥÈÉÊỀẾỄỂẼĒḔḖĔĖËẺĚȄȆẸỆȨḜĘḘḚƐƎ"},{base:"F",letters:"FⒻＦḞƑꝻ"},{base:"G",letters:"GⒼＧǴĜḠĞĠǦĢǤƓꞠꝽꝾ"},{base:"H",letters:"HⒽＨĤḢḦȞḤḨḪĦⱧⱵꞍ"},{base:"I",letters:"IⒾＩÌÍÎĨĪĬİÏḮỈǏȈȊỊĮḬƗ"},{base:"J",letters:"JⒿＪĴɈ"},{base:"K",letters:"KⓀＫḰǨḲĶḴƘⱩꝀꝂꝄꞢ"},{base:"L",letters:"LⓁＬĿĹĽḶḸĻḼḺŁȽⱢⱠꝈꝆꞀ"},{base:"LJ",letters:"Ǉ"},{base:"Lj",letters:"ǈ"},{base:"M",letters:"MⓂＭḾṀṂⱮƜ"},{base:"N",letters:"NⓃＮǸŃÑṄŇṆŅṊṈȠƝꞐꞤ"},{base:"NJ",letters:"Ǌ"},{base:"Nj",letters:"ǋ"},{base:"O",letters:"OⓄＯÒÓÔỒỐỖỔÕṌȬṎŌṐṒŎȮȰÖȪỎŐǑȌȎƠỜỚỠỞỢỌỘǪǬØǾƆƟꝊꝌ"},{base:"OI",letters:"Ƣ"},{base:"OO",letters:"Ꝏ"},{base:"OU",letters:"Ȣ"},{base:"OE",letters:"Œ"},{base:"oe",letters:"œ"},{base:"P",letters:"PⓅＰṔṖƤⱣꝐꝒꝔ"},{base:"Q",letters:"QⓆＱꝖꝘɊ"},{base:"R",letters:"RⓇＲŔṘŘȐȒṚṜŖṞɌⱤꝚꞦꞂ"},{base:"S",letters:"SⓈＳẞŚṤŜṠŠṦṢṨȘŞⱾꞨꞄ"},{base:"T",letters:"TⓉＴṪŤṬȚŢṰṮŦƬƮȾꞆ"},{base:"TZ",letters:"Ꜩ"},{base:"U",letters:"UⓊＵÙÚÛŨṸŪṺŬÜǛǗǕǙỦŮŰǓȔȖƯỪỨỮỬỰỤṲŲṶṴɄ"},{base:"V",letters:"VⓋＶṼṾƲꝞɅ"},{base:"VY",letters:"Ꝡ"},{base:"W",letters:"WⓌＷẀẂŴẆẄẈⱲ"},{base:"X",letters:"XⓍＸẊẌ"},{base:"Y",letters:"YⓎＹỲÝŶỸȲẎŸỶỴƳɎỾ"},{base:"Z",letters:"ZⓏＺŹẐŻŽẒẔƵȤⱿⱫꝢ"},{base:"a",letters:"aⓐａẚàáâầấẫẩãāăằắẵẳȧǡäǟảåǻǎȁȃạậặḁąⱥɐ"},{base:"aa",letters:"ꜳ"},{base:"ae",letters:"æǽǣ"},{base:"ao",letters:"ꜵ"},{base:"au",letters:"ꜷ"},{base:"av",letters:"ꜹꜻ"},{base:"ay",letters:"ꜽ"},{base:"b",letters:"bⓑｂḃḅḇƀƃɓ"},{base:"c",letters:"cⓒｃćĉċčçḉƈȼꜿↄ"},{base:"d",letters:"dⓓｄḋďḍḑḓḏđƌɖɗꝺ"},{base:"dz",letters:"ǳǆ"},{base:"e",letters:"eⓔｅèéêềếễểẽēḕḗĕėëẻěȅȇẹệȩḝęḙḛɇɛǝ"},{base:"f",letters:"fⓕｆḟƒꝼ"},{base:"g",letters:"gⓖｇǵĝḡğġǧģǥɠꞡᵹꝿ"},{base:"h",letters:"hⓗｈĥḣḧȟḥḩḫẖħⱨⱶɥ"},{base:"hv",letters:"ƕ"},{base:"i",letters:"iⓘｉìíîĩīĭïḯỉǐȉȋịįḭɨı"},{base:"j",letters:"jⓙｊĵǰɉ"},{base:"k",letters:"kⓚｋḱǩḳķḵƙⱪꝁꝃꝅꞣ"},{base:"l",letters:"lⓛｌŀĺľḷḹļḽḻſłƚɫⱡꝉꞁꝇ"},{base:"lj",letters:"ǉ"},{base:"m",letters:"mⓜｍḿṁṃɱɯ"},{base:"n",letters:"nⓝｎǹńñṅňṇņṋṉƞɲŉꞑꞥ"},{base:"nj",letters:"ǌ"},{base:"o",letters:"oⓞｏòóôồốỗổõṍȭṏōṑṓŏȯȱöȫỏőǒȍȏơờớỡởợọộǫǭøǿɔꝋꝍɵ"},{base:"oi",letters:"ƣ"},{base:"ou",letters:"ȣ"},{base:"oo",letters:"ꝏ"},{base:"p",letters:"pⓟｐṕṗƥᵽꝑꝓꝕ"},{base:"q",letters:"qⓠｑɋꝗꝙ"},{base:"r",letters:"rⓡｒŕṙřȑȓṛṝŗṟɍɽꝛꞧꞃ"},{base:"s",letters:"sⓢｓßśṥŝṡšṧṣṩșşȿꞩꞅẛ"},{base:"t",letters:"tⓣｔṫẗťṭțţṱṯŧƭʈⱦꞇ"},{base:"tz",letters:"ꜩ"},{base:"u",letters:"uⓤｕùúûũṹūṻŭüǜǘǖǚủůűǔȕȗưừứữửựụṳųṷṵʉ"},{base:"v",letters:"vⓥｖṽṿʋꝟʌ"},{base:"vy",letters:"ꝡ"},{base:"w",letters:"wⓦｗẁẃŵẇẅẘẉⱳ"},{base:"x",letters:"xⓧｘẋẍ"},{base:"y",letters:"yⓨｙỳýŷỹȳẏÿỷẙỵƴɏỿ"},{base:"z",letters:"zⓩｚźẑżžẓẕƶȥɀⱬꝣ"}],c={},d=0;d<b.length;d++)for(var e=b[d].letters.split(""),f=0;f<e.length;f++)c[e[f]]=b[d].base;return function(b){return A(b)?a(b):b}}]),b.module("a8m.ltrim",[]).filter("ltrim",function(){return function(a,b){var c=b||"\\s";return A(a)?a.replace(new RegExp("^"+c+"+"),""):a}}),b.module("a8m.match",[]).filter("match",function(){return function(a,b,c){var d=new RegExp(b,c);return A(a)?a.match(d):null}}),b.module("a8m.repeat",[]).filter("repeat",[function(){return function(a,b,c){var d=~~b;return A(a)&&d?w(a,--b,c||""):a}}]),b.module("a8m.rtrim",[]).filter("rtrim",function(){return function(a,b){var c=b||"\\s";return A(a)?a.replace(new RegExp(c+"+$"),""):a}}),b.module("a8m.slugify",[]).filter("slugify",[function(){return function(a,b){var c=y(b)?"-":b;return A(a)?a.toLowerCase().replace(/\s+/g,c):a}}]),b.module("a8m.starts-with",[]).filter("startsWith",function(){return function(a,b,c){var d=c||!1;return!A(a)||y(b)?a:(a=d?a:a.toLowerCase(),!a.indexOf(d?b:b.toLowerCase()))}}),b.module("a8m.stringular",[]).filter("stringular",function(){return function(a){var b=Array.prototype.slice.call(arguments,1);return a.replace(/{(\d+)}/g,function(a,c){return y(b[c])?a:b[c]})}}),b.module("a8m.strip-tags",[]).filter("stripTags",function(){return function(a){return A(a)?a.replace(/<\S[^><]*>/g,""):a}}),b.module("a8m.test",[]).filter("test",function(){return function(a,b,c){var d=new RegExp(b,c);return A(a)?d.test(a):a}}),b.module("a8m.trim",[]).filter("trim",function(){return function(a,b){var c=b||"\\s";return A(a)?a.replace(new RegExp("^"+c+"+|"+c+"+$","g"),""):a}}),b.module("a8m.truncate",[]).filter("truncate",function(){return function(a,b,c,d){return b=y(b)?a.length:b,d=d||!1,c=c||"",!A(a)||a.length<=b?a:a.substring(0,d?a.indexOf(" ",b)===-1?a.length:a.indexOf(" ",b):b)+c}}),b.module("a8m.ucfirst",[]).filter("ucfirst",[function(){return function(a){return A(a)?a.split(" ").map(function(a){return a.charAt(0).toUpperCase()+a.substring(1)}).join(" "):a}}]),b.module("a8m.uri-component-encode",[]).filter("uriComponentEncode",["$window",function(a){return function(b){return A(b)?a.encodeURIComponent(b):b}}]),b.module("a8m.uri-encode",[]).filter("uriEncode",["$window",function(a){return function(b){return A(b)?a.encodeURI(b):b}}]),b.module("a8m.wrap",[]).filter("wrap",function(){return function(a,b,c){return A(a)&&x(b)?[b,a,c||b].join(""):a}}),b.module("a8m.filter-watcher",[]).provider("filterWatcher",function(){this.$get=["$window","$rootScope",function(a,b){function c(b,c){function d(){var b=[];return function(c,d){if(C(d)&&!e(d)){if(~b.indexOf(d))return"[Circular]";b.push(d)}return a==d?"$WINDOW":a.document==d?"$DOCUMENT":k(d)?"$SCOPE":d}}return[b,JSON.stringify(c,d())].join("#").replace(/"/g,"")}function d(a){var b=a.targetScope.$id;E(l[b],function(a){delete j[a]}),delete l[b]}function f(){m(function(){b.$$phase||(j={})},2e3)}function g(a,b){var c=a.$id;return y(l[c])&&(a.$on("$destroy",d),l[c]=[]),l[c].push(b)}function h(a,b){var d=c(a,b);return j[d]}function i(a,b,d,e){var h=c(a,b);return j[h]=e,k(d)?g(d,h):f(),e}var j={},l={},m=a.setTimeout;return{isMemoized:h,memoize:i}}]}),b.module("angular.filter",["a8m.ucfirst","a8m.uri-encode","a8m.uri-component-encode","a8m.slugify","a8m.latinize","a8m.strip-tags","a8m.stringular","a8m.truncate","a8m.starts-with","a8m.ends-with","a8m.wrap","a8m.trim","a8m.ltrim","a8m.rtrim","a8m.repeat","a8m.test","a8m.match","a8m.to-array","a8m.concat","a8m.contains","a8m.unique","a8m.is-empty","a8m.after","a8m.after-where","a8m.before","a8m.before-where","a8m.defaults","a8m.where","a8m.reverse","a8m.remove","a8m.remove-with","a8m.group-by","a8m.count-by","a8m.chunk-by","a8m.search-field","a8m.fuzzy-by","a8m.fuzzy","a8m.omit","a8m.pick","a8m.every","a8m.filter-by","a8m.xor","a8m.map","a8m.first","a8m.last","a8m.flatten","a8m.join","a8m.range","a8m.math","a8m.math.max","a8m.math.min","a8m.math.percent","a8m.math.radix","a8m.math.sum","a8m.math.degrees","a8m.math.radians","a8m.math.byteFmt","a8m.math.kbFmt","a8m.math.shortFmt","a8m.angular","a8m.conditions","a8m.is-null","a8m.filter-watcher"])}(window,window.angular);
/*
 AngularJS v1.5.8
 (c) 2010-2016 Google, Inc. http://angularjs.org
 License: MIT
*/
(function(n,c){'use strict';function l(b,a,g){var d=g.baseHref(),k=b[0];return function(b,e,f){var g,h;f=f||{};h=f.expires;g=c.isDefined(f.path)?f.path:d;c.isUndefined(e)&&(h="Thu, 01 Jan 1970 00:00:00 GMT",e="");c.isString(h)&&(h=new Date(h));e=encodeURIComponent(b)+"="+encodeURIComponent(e);e=e+(g?";path="+g:"")+(f.domain?";domain="+f.domain:"");e+=h?";expires="+h.toUTCString():"";e+=f.secure?";secure":"";f=e.length+1;4096<f&&a.warn("Cookie '"+b+"' possibly not set or overflowed because it was too large ("+
f+" > 4096 bytes)!");k.cookie=e}}c.module("ngCookies",["ng"]).provider("$cookies",[function(){var b=this.defaults={};this.$get=["$$cookieReader","$$cookieWriter",function(a,g){return{get:function(d){return a()[d]},getObject:function(d){return(d=this.get(d))?c.fromJson(d):d},getAll:function(){return a()},put:function(d,a,m){g(d,a,m?c.extend({},b,m):b)},putObject:function(d,b,a){this.put(d,c.toJson(b),a)},remove:function(a,k){g(a,void 0,k?c.extend({},b,k):b)}}}]}]);c.module("ngCookies").factory("$cookieStore",
["$cookies",function(b){return{get:function(a){return b.getObject(a)},put:function(a,c){b.putObject(a,c)},remove:function(a){b.remove(a)}}}]);l.$inject=["$document","$log","$browser"];c.module("ngCookies").provider("$$cookieWriter",function(){this.$get=l})})(window,window.angular);
//# sourceMappingURL=angular-cookies.min.js.map

'use strict';

angular.module('AutoSMART.Web', [
    'ui.router', 'AutoSMART.Web.Common', 'ngCookies',
    'AutoSMART.Web.CertificateProcess', 'AutoSMART.Web.Search',
    'AutoSMART.Web.VehicleDetail', 'AutoSMART.Web.Dealer',
    'AutoSMART.Web.HomePage', 'AutoSMART.Web.Finance', 'AutoSMART.Web.Build', 'AutoSMART.Web.PreQualify',
    'AutoSMART.Web.MemberCertificate', 'AutoSMART.Web.LendingTree', 'ngAria', 'bw.paging', 'AutoSMART.QQResult', 'AutoSMART.QQNoResults'])
    .config(['$urlMatcherFactoryProvider', '$cookiesProvider', '$stateProvider', '$urlRouterProvider', 'insightsProvider', 'Config',
        function ($urlMatcherFactoryProvider, $cookiesProvider, $stateProvider, $urlRouterProvider, insightsProvider, Config) {
            $cookiesProvider.defaults.path = '/';
            $cookiesProvider.defaults.secure = true;


            $urlMatcherFactoryProvider.caseInsensitive(true);
            $urlMatcherFactoryProvider.strictMode(false);

            $stateProvider
                .state('search', {
                    url: "/search",
                    template: "<inventorycomponent></inventorycomponent>"
                })

                .state('dedicateddealersearch', {
                    url: "/search?dealerclientcode2={dealerclientcode}&ItemType={itemtype}&Condition={condition}&=Makes={make}",
                    template: "<inventorycomponent></inventorycomponent>",
                    controller: ["$scope", "$stateParams", function ($scope, $stateParams) {
                        $scope.dealerclientcode = $stateParams.dealerclientcode;
                    }]
                })

                .state('dealer', {
                    url: "/dealer",
                    template: "<dealersearchcomponent></dealersearchcomponent>"
                })

                .state('build', {
                    url: '/v4/build',
                    template: '<builddealerselectioncomponent></builddealerselectioncomponent>'
                })

                .state('finance', {
                    url: '/finance',
                    template: '<financecomponent></financecomponent>'
                })

                .state('detail', {
                    url: "/vehicle/details/{itemid}?isSpotlight={isSpotlightAd}",
                    params: { sourcefrom: null },
                    template: "<vehicledetailcomponent></vehicledetailcomponent>",
                    controller: ["$scope", "$stateParams", function ($scope, $stateParams) {
                        $scope.itemid = $stateParams.itemid;
                        $scope.isSpotlight = $stateParams.isSpotlight;
                        $scope.sourcefrom = $stateParams.sourcefrom;
                    }]
                });
            $urlRouterProvider.otherwise("/search");
            insightsProvider.start(Config.InsightsAppId, 'Autosmart V4', null);
        }
    ]);

//angular.module('AutoSMART.Web', []).run(function () {
//    console.log("app run");
//});

//angular.module('AutoSMART.Web', [
//    'ui.router', 'AutoSMART.Web.Common', 'ngCookies',
//    'AutoSMART.Web.CertificateProcess', 'AutoSMART.Web.Search',
//    'AutoSMART.Web.VehicleDetail', 'AutoSMART.Web.Dealer',
//    'AutoSMART.Web.HomePage', 'AutoSMART.Web.Finance', 'AutoSMART.Web.Build', 'AutoSMART.Web.PreQualify',
//    'AutoSMART.Web.MemberCertificate', 'AutoSMART.Web.LendingTree', 'ngAria', 'bw.paging',
//    'AutoSMART.QQResult', 'AutoSMART.QQNoResults']).run(function ($rootScope, $timeout) {
//    $rootScope.$watch(function () {
//        $timeout(function () {
//            dataLayer.push({ 'event': 'optimize.activate' });
//        }, 0, false);
//    })
//})

"use strict";
angular.module('AutoSMART.Web.Search', ['ui.router', 'AutoSMART.Web.Common', 'angular.filter', 'rzModule', 'ui.bootstrap', 'ngCookies']);


"use strict";
angular.module('AutoSMART.Web.VehicleDetail', ['ui.router', 'AutoSMART.Web.Common', 'angular.filter', 'rzModule', 'ui.bootstrap', 'ngCookies', 'ngAnimate']);

 
"use strict";
angular.module('AutoSMART.Web.Dealer', ['ui.router', 'AutoSMART.Web.Common', 'angular.filter', 'rzModule', 'ui.bootstrap', 'ngCookies']);


"use strict";

(function (module) {

    module.component('dealersearchcomponent', {
       
        controller: ["$scope", "$location", "Config", "DealerSearchFilterService", "DealerSearchService", "insights",
            function ($scope, $location, Config, DealerSearchFilterService, DealerSearchService, insights) {
                var ctrl = this;

                ctrl.Loading = false;  // need to fix this
                ctrl.screenWidth = window.innerWidth;
                ctrl.screenHeight = window.innerHeight;

                $scope.toggleFilterListDisplay = function (obj, $event) {
                    var $e = $($event.currentTarget);
                    var $i = $e.find('i');
                    var $div = $e.find('div');
                    var $filters = $('#filters');
                    var $results = $('#search-results-list');

                    if ($filters.is(':hidden')) {
                        $results.fadeOut(150, function () {
                            $(this).addClass('hidden-xs');
                            $filters.removeClass('hidden-xs hidden-sm').fadeIn(250);
                            $div.text('done');
                            $i.removeClass('fa-sliders').addClass('fa-times');
                        });
                    } else {
                        $filters.fadeOut(150, function () {
                            $(this).addClass('hidden-xs');
                            $results.removeClass('hidden-xs hidden-sm').fadeIn(250);
                            $div.text('filters');
                            $i.removeClass('fa-sliders').addClass('fa-sliders');
                        });
                    }
                };


                $scope.setupFilters = function () {
                    window.setTimeout(function () {
                        $('.filter .header .toggler').on('click', function () {
                            $scope.toggleFilter(this);
                        });

                        $('.scrollbar-light').scrollbar();

                    }, 100);
                };


                ctrl.$onInit = function () {
                    var correlationId = insights.randomString(5);
                    insights.logEvent("Dealer Search Result Page load start", { 'tenentId': Config.ClientCode, "correlationId": correlationId });
                    document.title = "AutoSMART - Search for Dealers";
                    ctrl.selectedFilters = DealerSearchFilterService.selectedFilters;

                    DealerSearchFilterService.Init().then(function () {

                        ctrl.filterOptions = DealerSearchFilterService.filterOptions;

                        DealerSearchService.RetrieveDealers(true).then(function () {
                            ctrl.allDealers = DealerSearchService.allDealers;
                            ctrl.Loading = false;
                            insights.logEvent("Dealer Search Result Page load finish", { 'tenentId': Config.ClientCode, "correlationId": correlationId });
                        });
                        //amplitude.getInstance().logEvent('Viewed Dealer Search Page');

                       $scope.setupFilters();
                    });
                };


                $scope.toggleFilter = function (elem) {
                    var $elem = $(elem);
                    var $items = $elem.parents('.filter').find('.items');
                    var goToHeight = 0;

                    if ($items.height() === 0) {
                        var $sliderControl = $items.find('.slider-control');
                        if ($sliderControl.length > 0) {
                            if ($sliderControl.attr('data-toggle-height')) {
                                goToHeight = $sliderControl.data('toggle-height');
                            } else {
                                goToHeight = 85;
                            }
                        } else {
                            if ($items.attr('data-max-height')) {
                                goToHeight = $items.data('max-height');
                            } else {
                                goToHeight = 275;
                            }
                        }
                    }

                    $items.velocity({
                        height: goToHeight
                    }, 500, function () {
                        if ($elem.hasClass('toggler-down')) {
                            $elem.addClass('toggler-up').removeClass('toggler-down');
                        } else {
                            $elem.addClass('toggler-down').removeClass('toggler-up');
                        }

                    }, 'easeInOutQuad');
                };


            }],
                templateUrl: UrlContent('/App/Dealer/Views/DealerSearchComponent.html')
            });
})(angular.module('AutoSMART.Web.Dealer'));

"use strict";
angular.module('AutoSMART.Web.Build', ['ui.router', 'AutoSMART.Web.Common', 'AutoSMART.Web.Dealer', 'angular.filter', 'rzModule', 'ui.bootstrap', 'ngCookies']);


"use strict";
angular
    .module('AutoSMART.Web.Common', ['ui.router', 'ui.bootstrap', 'ngAria','ngCookies', 'ngAnimate','angular.filter']);
    

angular.module('AutoSMART.Web.Common')
    .directive('dealerRating', function () {

        function dealerRatingIcons(dealerRating) {
            var htmlContent = "";
            var totalIcons = 5;

            if (dealerRating && dealerRating > 0) {
                var currentIconCount = 0;
                for (var i = 1; i <= dealerRating; i++) {
                    htmlContent += "<i class=\"fa fa-star\"></i>";
                    currentIconCount++;
                }
                if ((dealerRating % 1) !== 0) {
                    htmlContent += "<i class=\"fa fa-star-half-full\" ></i>";
                    currentIconCount++;
                }
                if (dealerRating !== totalIcons) {
                    for (var i = currentIconCount; i < totalIcons; i++) {
                        htmlContent += "<i class=\"fa fa-star-o\"></i>";
                    }
                }
                
            } else {
                for (var i = 0; i < totalIcons; i++) {
                    htmlContent += "<i class=\"fa fa-star-o\"></i>";
                }
            }
            return htmlContent;
        }

        return {
            scope: { iconNum: '@' },
            link: function (scope, element) {
                var result = dealerRatingIcons(scope.iconNum);
                element.html(result);
            }
        };
    });
/*! angularjs-slider - v6.5.0 - 
 (c) Rafal Zajac <rzajac@gmail.com>, Valentin Hervieu <valentin@hervieu.me>, Jussi Saarivirta <jusasi@gmail.com>, Angelin Sirbu <angelin.sirbu@gmail.com> - 
 https://github.com/angular-slider/angularjs-slider - 
 2018-02-07 */
/*jslint unparam: true */
/*global angular: false, console: false, define, module */
; (function (root, factory) {
    'use strict'
    /* istanbul ignore next */
    if (typeof define === 'function' && define.amd) {
        // AMD. Register as an anonymous module.
        define(['angular'], factory)
    } else if (typeof module === 'object' && module.exports) {
        // Node. Does not work with strict CommonJS, but
        // only CommonJS-like environments that support module.exports,
        // like Node.
        // to support bundler like browserify
        var angularObj = angular || require('angular')
        if ((!angularObj || !angularObj.module) && typeof angular != 'undefined') {
            angularObj = angular
        }
        module.exports = factory(angularObj)
    } else {
        // Browser globals (root is window)
        factory(root.angular)
    }
})(this, function (angular) {
    'use strict'
    var module = angular
        .module('rzModule', [])
        .factory('RzSliderOptions', function () {
            var defaultOptions = {
                floor: 0,
                ceil: null, //defaults to rz-slider-model
                step: 1,
                precision: 0,
                minRange: null,
                maxRange: null,
                pushRange: false,
                minLimit: null,
                maxLimit: null,
                id: null,
                translate: null,
                getLegend: null,
                stepsArray: null,
                bindIndexForStepsArray: false,
                draggableRange: false,
                draggableRangeOnly: false,
                showSelectionBar: false,
                showSelectionBarEnd: false,
                showSelectionBarFromValue: null,
                showOuterSelectionBars: false,
                hidePointerLabels: false,
                hideLimitLabels: false,
                autoHideLimitLabels: true,
                readOnly: false,
                disabled: false,
                interval: 350,
                showTicks: false,
                showTicksValues: false,
                ticksArray: null,
                ticksTooltip: null,
                ticksValuesTooltip: null,
                vertical: false,
                getSelectionBarColor: null,
                getTickColor: null,
                getPointerColor: null,
                keyboardSupport: true,
                scale: 1,
                enforceStep: true,
                enforceRange: false,
                noSwitching: false,
                onlyBindHandles: false,
                onStart: null,
                onChange: null,
                onEnd: null,
                rightToLeft: false,
                reversedControls: false,
                boundPointerLabels: true,
                mergeRangeLabelsIfSame: false,
                labelOverlapSeparator: ' - ',
                customTemplateScope: null,
                logScale: false,
                customValueToPosition: null,
                customPositionToValue: null,
                selectionBarGradient: null,
                ariaLabel: null,
                ariaLabelledBy: null,
                ariaLabelHigh: null,
                ariaLabelledByHigh: null,
            }
            var globalOptions = {}

            var factory = {}
            /**
             * `options({})` allows global configuration of all sliders in the
             * application.
             *
             *   var app = angular.module( 'App', ['rzModule'], function( RzSliderOptions ) {
             *     // show ticks for all sliders
             *     RzSliderOptions.options( { showTicks: true } );
             *   });
             */
            factory.options = function (value) {
                angular.extend(globalOptions, value)
            }

            factory.getOptions = function (options) {
                return angular.extend({}, defaultOptions, globalOptions, options)
            }

            return factory
        })
        .factory('rzThrottle', ['$timeout', function ($timeout) {
            /**
             * rzThrottle
             *
             * Taken from underscore project
             *
             * @param {Function} func
             * @param {number} wait
             * @param {ThrottleOptions} options
             * @returns {Function}
             */
            return function (func, wait, options) {
                'use strict'
                /* istanbul ignore next */
                var getTime =
                    Date.now ||
                    function () {
                        return new Date().getTime()
                    }
                var context, args, result
                var timeout = null
                var previous = 0
                options = options || {}
                var later = function () {
                    previous = getTime()
                    timeout = null
                    result = func.apply(context, args)
                    context = args = null
                }
                return function () {
                    var now = getTime()
                    var remaining = wait - (now - previous)
                    context = this
                    args = arguments
                    if (remaining <= 0) {
                        $timeout.cancel(timeout)
                        timeout = null
                        previous = now
                        result = func.apply(context, args)
                        context = args = null
                    } else if (!timeout && options.trailing !== false) {
                        timeout = $timeout(later, remaining)
                    }
                    return result
                }
            }
        }])
        .factory('RzSlider', ['$timeout', '$document', '$window', '$compile', 'RzSliderOptions', 'rzThrottle', function (
            $timeout,
            $document,
            $window,
            $compile,
            RzSliderOptions,
            rzThrottle
        ) {
            'use strict'

            /**
             * Slider
             *
             * @param {ngScope} scope            The AngularJS scope
             * @param {Element} sliderElem The slider directive element wrapped in jqLite
             * @constructor
             */
            var Slider = function (scope, sliderElem) {
                /**
                 * The slider's scope
                 *
                 * @type {ngScope}
                 */
                this.scope = scope

                /**
                 * The slider inner low value (linked to rzSliderModel)
                 * @type {number}
                 */
                this.lowValue = 0

                /**
                 * The slider inner high value (linked to rzSliderHigh)
                 * @type {number}
                 */
                this.highValue = 0

                /**
                 * Slider element wrapped in jqLite
                 *
                 * @type {jqLite}
                 */
                this.sliderElem = sliderElem

                /**
                 * Slider type
                 *
                 * @type {boolean} Set to true for range slider
                 */
                this.range =
                    this.scope.rzSliderModel !== undefined &&
                    this.scope.rzSliderHigh !== undefined

                /**
                 * Values recorded when first dragging the bar
                 *
                 * @type {Object}
                 */
                this.dragging = {
                    active: false,
                    value: 0,
                    difference: 0,
                    position: 0,
                    lowLimit: 0,
                    highLimit: 0,
                }

                /**
                 * property that handle position (defaults to left for horizontal)
                 * @type {string}
                 */
                this.positionProperty = 'left'

                /**
                 * property that handle dimension (defaults to width for horizontal)
                 * @type {string}
                 */
                this.dimensionProperty = 'width'

                /**
                 * Half of the width or height of the slider handles
                 *
                 * @type {number}
                 */
                this.handleHalfDim = 0

                /**
                 * Maximum position the slider handle can have
                 *
                 * @type {number}
                 */
                this.maxPos = 0

                /**
                 * Precision
                 *
                 * @type {number}
                 */
                this.precision = 0

                /**
                 * Step
                 *
                 * @type {number}
                 */
                this.step = 1

                /**
                 * The name of the handle we are currently tracking
                 *
                 * @type {string}
                 */
                this.tracking = ''

                /**
                 * Minimum value (floor) of the model
                 *
                 * @type {number}
                 */
                this.minValue = 0

                /**
                 * Maximum value (ceiling) of the model
                 *
                 * @type {number}
                 */
                this.maxValue = 0

                /**
                 * The delta between min and max value
                 *
                 * @type {number}
                 */
                this.valueRange = 0

                /**
                 * If showTicks/showTicksValues options are number.
                 * In this case, ticks values should be displayed below the slider.
                 * @type {boolean}
                 */
                this.intermediateTicks = false

                /**
                 * Set to true if init method already executed
                 *
                 * @type {boolean}
                 */
                this.initHasRun = false

                /**
                 * Used to call onStart on the first keydown event
                 *
                 * @type {boolean}
                 */
                this.firstKeyDown = false

                /**
                * Used to call onfocus to avoid submit data
                *
                * @type {boolean}
                */

                this.gotFocus = false

                /**
                 * Internal flag to prevent watchers to be called when the sliders value are modified internally.
                 * @type {boolean}
                 */
                this.internalChange = false

                /**
                 * Internal flag to keep track of the visibility of combo label
                 * @type {boolean}
                 */
                this.cmbLabelShown = false

                /**
                 * Internal variable to keep track of the focus element
                 */
                this.currentFocusElement = null

                // Slider DOM elements wrapped in jqLite
                this.fullBar = null // The whole slider bar
                this.selBar = null // Highlight between two handles
                this.minH = null // Left slider handle
                this.maxH = null // Right slider handle
                this.flrLab = null // Floor label
                this.ceilLab = null // Ceiling label
                this.minLab = null // Label above the low value
                this.maxLab = null // Label above the high value
                this.cmbLab = null // Combined label
                this.ticks = null // The ticks

                // Initialize slider
                this.init()
            }

            // Add instance methods
            Slider.prototype = {
                /**
                 * Initialize slider
                 *
                 * @returns {undefined}
                 */
                init: function () {
                    var thrLow,
                        thrHigh,
                        self = this

                    var calcDimFn = function () {
                        self.calcViewDimensions()
                    }

                    this.applyOptions()
                    this.syncLowValue()
                    if (this.range) this.syncHighValue()
                    this.initElemHandles()
                    this.manageElementsStyle()
                    this.setDisabledState()
                    this.calcViewDimensions()
                    this.setMinAndMax()
                    this.addAccessibility()
                    this.updateCeilLab()
                    this.updateFloorLab()
                    this.initHandles()
                    this.manageEventsBindings()

                    // Recalculate slider view dimensions
                    this.scope.$on('reCalcViewDimensions', calcDimFn)

                    // Recalculate stuff if view port dimensions have changed
                    angular.element($window).on('resize', calcDimFn)

                    this.initHasRun = true

                    // Watch for changes to the model
                    thrLow = rzThrottle(function () {
                        self.onLowHandleChange()
                    }, self.options.interval)

                    thrHigh = rzThrottle(function () {
                        self.onHighHandleChange()
                    }, self.options.interval)

                    this.scope.$on('rzSliderForceRender', function () {
                        self.resetLabelsValue()
                        thrLow()
                        if (self.range) {
                            thrHigh()
                        }
                        self.resetSlider()
                    })

                    // Watchers (order is important because in case of simultaneous change,
                    // watchers will be called in the same order)
                    this.scope.$watchCollection('rzSliderOptions()', function (
                        newValue,
                        oldValue
                    ) {
                        if (newValue === oldValue) return
                        self.applyOptions() // need to be called before synchronizing the values
                        self.syncLowValue()
                        if (self.range) self.syncHighValue()
                        self.resetSlider()
                    })

                    this.scope.$watch('rzSliderModel', function (newValue, oldValue) {
                        if (self.internalChange) return
                        if (newValue === oldValue) return
                        thrLow()
                    })

                    this.scope.$watch('rzSliderHigh', function (newValue, oldValue) {
                        if (self.internalChange) return
                        if (newValue === oldValue) return
                        if (newValue != null) thrHigh()
                        if (
                            (self.range && newValue == null) ||
                            (!self.range && newValue != null)
                        ) {
                            self.applyOptions()
                            self.resetSlider()
                        }
                    })

                    this.scope.$on('$destroy', function () {
                        self.unbindEvents()
                        angular.element($window).off('resize', calcDimFn)
                        self.currentFocusElement = null
                    })
                },

                findStepIndex: function (modelValue) {
                    var index = 0
                    for (var i = 0; i < this.options.stepsArray.length; i++) {
                        var step = this.options.stepsArray[i]
                        if (step === modelValue) {
                            index = i
                            break
                        } else if (angular.isDate(step)) {
                            if (step.getTime() === modelValue.getTime()) {
                                index = i
                                break
                            }
                        } else if (angular.isObject(step)) {
                            if (
                                (angular.isDate(step.value) &&
                                    step.value.getTime() === modelValue.getTime()) ||
                                step.value === modelValue
                            ) {
                                index = i
                                break
                            }
                        }
                    }
                    return index
                },

                syncLowValue: function () {
                    if (this.options.stepsArray) {
                        if (!this.options.bindIndexForStepsArray)
                            this.lowValue = this.findStepIndex(this.scope.rzSliderModel)
                        else this.lowValue = this.scope.rzSliderModel
                    } else this.lowValue = this.scope.rzSliderModel
                },

                syncHighValue: function () {
                    if (this.options.stepsArray) {
                        if (!this.options.bindIndexForStepsArray)
                            this.highValue = this.findStepIndex(this.scope.rzSliderHigh)
                        else this.highValue = this.scope.rzSliderHigh
                    } else this.highValue = this.scope.rzSliderHigh
                },

                getStepValue: function (sliderValue) {
                    var step = this.options.stepsArray[sliderValue]
                    if (angular.isDate(step)) return step
                    if (angular.isObject(step)) return step.value
                    return step
                },

                applyLowValue: function () {
                    if (this.options.stepsArray) {
                        if (!this.options.bindIndexForStepsArray)
                            this.scope.rzSliderModel = this.getStepValue(this.lowValue)
                        else this.scope.rzSliderModel = this.lowValue
                    } else this.scope.rzSliderModel = this.lowValue
                },

                applyHighValue: function () {
                    if (this.options.stepsArray) {
                        if (!this.options.bindIndexForStepsArray)
                            this.scope.rzSliderHigh = this.getStepValue(this.highValue)
                        else this.scope.rzSliderHigh = this.highValue
                    } else this.scope.rzSliderHigh = this.highValue
                },

                /*
               * Reflow the slider when the low handle changes (called with throttle)
               */
                onLowHandleChange: function () {
                    this.syncLowValue()
                    if (this.range) this.syncHighValue()
                    this.setMinAndMax()
                    this.updateLowHandle(this.valueToPosition(this.lowValue))
                    this.updateSelectionBar()
                    this.updateTicksScale()
                    this.updateAriaAttributes()
                    if (this.range) {
                        this.updateCmbLabel()
                    }
                },

                /*
               * Reflow the slider when the high handle changes (called with throttle)
               */
                onHighHandleChange: function () {
                    this.syncLowValue()
                    this.syncHighValue()
                    this.setMinAndMax()
                    this.updateHighHandle(this.valueToPosition(this.highValue))
                    this.updateSelectionBar()
                    this.updateTicksScale()
                    this.updateCmbLabel()
                    this.updateAriaAttributes()
                },

                /**
                 * Read the user options and apply them to the slider model
                 */
                applyOptions: function () {
                    var sliderOptions
                    if (this.scope.rzSliderOptions)
                        sliderOptions = this.scope.rzSliderOptions()
                    else sliderOptions = {}

                    this.options = RzSliderOptions.getOptions(sliderOptions)

                    if (this.options.step <= 0) this.options.step = 1

                    this.range =
                        this.scope.rzSliderModel !== undefined &&
                        this.scope.rzSliderHigh !== undefined
                    this.options.draggableRange =
                        this.range && this.options.draggableRange
                    this.options.draggableRangeOnly =
                        this.range && this.options.draggableRangeOnly
                    if (this.options.draggableRangeOnly) {
                        this.options.draggableRange = true
                    }

                    this.options.showTicks =
                        this.options.showTicks ||
                        this.options.showTicksValues ||
                        !!this.options.ticksArray
                    this.scope.showTicks = this.options.showTicks //scope is used in the template
                    if (
                        angular.isNumber(this.options.showTicks) ||
                        this.options.ticksArray
                    )
                        this.intermediateTicks = true

                    this.options.showSelectionBar =
                        this.options.showSelectionBar ||
                        this.options.showSelectionBarEnd ||
                        this.options.showSelectionBarFromValue !== null

                    if (this.options.stepsArray) {
                        this.parseStepsArray()
                    } else {
                        if (this.options.translate) this.customTrFn = this.options.translate
                        else
                            this.customTrFn = function (value) {
                                return String(value)
                            }

                        this.getLegend = this.options.getLegend
                    }

                    if (this.options.vertical) {
                        this.positionProperty = 'bottom'
                        this.dimensionProperty = 'height'
                    }

                    if (this.options.customTemplateScope)
                        this.scope.custom = this.options.customTemplateScope
                },

                parseStepsArray: function () {
                    this.options.floor = 0
                    this.options.ceil = this.options.stepsArray.length - 1
                    this.options.step = 1

                    if (this.options.translate) {
                        this.customTrFn = this.options.translate
                    } else {
                        this.customTrFn = function (modelValue) {
                            if (this.options.bindIndexForStepsArray)
                                return this.getStepValue(modelValue)
                            return modelValue
                        }
                    }

                    this.getLegend = function (index) {
                        var step = this.options.stepsArray[index]
                        if (angular.isObject(step)) return step.legend
                        return null
                    }
                },

                /**
                 * Resets slider
                 *
                 * @returns {undefined}
                 */
                resetSlider: function () {
                    this.manageElementsStyle()
                    this.addAccessibility()
                    this.setMinAndMax()
                    this.updateCeilLab()
                    this.updateFloorLab()
                    this.unbindEvents()
                    this.manageEventsBindings()
                    this.setDisabledState()
                    this.calcViewDimensions()
                    this.refocusPointerIfNeeded()
                },

                refocusPointerIfNeeded: function () {
                    if (this.currentFocusElement) {
                        this.onPointerFocus(
                            this.currentFocusElement.pointer,
                            this.currentFocusElement.ref
                        )
                        this.focusElement(this.currentFocusElement.pointer)
                    }
                },

                /**
                 * Set the slider children to variables for easy access
                 *
                 * Run only once during initialization
                 *
                 * @returns {undefined}
                 */
                initElemHandles: function () {
                    // Assign all slider elements to object properties for easy access
                    angular.forEach(
                        this.sliderElem.children(),
                        function (elem, index) {
                            var jElem = angular.element(elem)

                            switch (index) {
                                case 0:
                                    this.leftOutSelBar = jElem
                                    break
                                case 1:
                                    this.rightOutSelBar = jElem
                                    break
                                case 2:
                                    this.fullBar = jElem
                                    break
                                case 3:
                                    this.selBar = jElem
                                    break
                                case 4:
                                    this.minH = jElem
                                    break
                                case 5:
                                    this.maxH = jElem
                                    break
                                case 6:
                                    this.flrLab = jElem
                                    break
                                case 7:
                                    this.ceilLab = jElem
                                    break
                                case 8:
                                    this.minLab = jElem
                                    break
                                case 9:
                                    this.maxLab = jElem
                                    break
                                case 10:
                                    this.cmbLab = jElem
                                    break
                                case 11:
                                    this.ticks = jElem
                                    break
                            }
                        },
                        this
                    )

                    // Initialize position cache properties
                    this.selBar.rzsp = 0
                    this.minH.rzsp = 0
                    this.maxH.rzsp = 0
                    this.flrLab.rzsp = 0
                    this.ceilLab.rzsp = 0
                    this.minLab.rzsp = 0
                    this.maxLab.rzsp = 0
                    this.cmbLab.rzsp = 0
                },

                /**
                 * Update each elements style based on options
                 */
                manageElementsStyle: function () {
                    if (!this.range) this.maxH.css('display', 'none')
                    else this.maxH.css('display', '')

                    this.alwaysHide(
                        this.flrLab,
                        this.options.showTicksValues || this.options.hideLimitLabels
                    )
                    this.alwaysHide(
                        this.ceilLab,
                        this.options.showTicksValues || this.options.hideLimitLabels
                    )

                    var hideLabelsForTicks =
                        this.options.showTicksValues && !this.intermediateTicks
                    this.alwaysHide(
                        this.minLab,
                        hideLabelsForTicks || this.options.hidePointerLabels
                    )
                    this.alwaysHide(
                        this.maxLab,
                        hideLabelsForTicks || !this.range || this.options.hidePointerLabels
                    )
                    this.alwaysHide(
                        this.cmbLab,
                        hideLabelsForTicks || !this.range || this.options.hidePointerLabels
                    )
                    this.alwaysHide(
                        this.selBar,
                        !this.range && !this.options.showSelectionBar
                    )
                    this.alwaysHide(
                        this.leftOutSelBar,
                        !this.range || !this.options.showOuterSelectionBars
                    )
                    this.alwaysHide(
                        this.rightOutSelBar,
                        !this.range || !this.options.showOuterSelectionBars
                    )

                    if (this.range && this.options.showOuterSelectionBars) {
                        this.fullBar.addClass('rz-transparent')
                    }

                    if (this.options.vertical) this.sliderElem.addClass('rz-vertical')

                    if (this.options.draggableRange) this.selBar.addClass('rz-draggable')
                    else this.selBar.removeClass('rz-draggable')

                    if (this.intermediateTicks && this.options.showTicksValues)
                        this.ticks.addClass('rz-ticks-values-under')
                },

                alwaysHide: function (el, hide) {
                    el.rzAlwaysHide = hide
                    if (hide) this.hideEl(el)
                    else this.showEl(el)
                },

                /**
                 * Manage the events bindings based on readOnly and disabled options
                 *
                 * @returns {undefined}
                 */
                manageEventsBindings: function () {
                    if (this.options.disabled || this.options.readOnly)
                        this.unbindEvents()
                    else this.bindEvents()
                },

                /**
                 * Set the disabled state based on rzSliderDisabled
                 *
                 * @returns {undefined}
                 */
                setDisabledState: function () {
                    if (this.options.disabled) {
                        this.sliderElem.attr('disabled', 'disabled')
                    } else {
                        this.sliderElem.attr('disabled', null)
                    }
                },

                /**
                 * Reset label values
                 *
                 * @return {undefined}
                 */
                resetLabelsValue: function () {
                    this.minLab.rzsv = undefined
                    this.maxLab.rzsv = undefined
                },

                /**
                 * Initialize slider handles positions and labels
                 *
                 * Run only once during initialization and every time view port changes size
                 *
                 * @returns {undefined}
                 */
                initHandles: function () {
                    this.updateLowHandle(this.valueToPosition(this.lowValue))

                    /*
                   the order here is important since the selection bar should be
                   updated after the high handle but before the combined label
                   */
                    if (this.range)
                        this.updateHighHandle(this.valueToPosition(this.highValue))
                    this.updateSelectionBar()
                    if (this.range) this.updateCmbLabel()

                    this.updateTicksScale()
                },

                /**
                 * Translate value to human readable format
                 *
                 * @param {number|string} value
                 * @param {jqLite} label
                 * @param {String} which
                 * @param {boolean} [useCustomTr]
                 * @returns {undefined}
                 */
                translateFn: function (value, label, which, useCustomTr) {
                    useCustomTr = useCustomTr === undefined ? true : useCustomTr

                    var valStr = '',
                        getDimension = false,
                        noLabelInjection = label.hasClass('no-label-injection')

                    if (useCustomTr) {
                        if (this.options.stepsArray && !this.options.bindIndexForStepsArray)
                            value = this.getStepValue(value)
                        valStr = String(this.customTrFn(value, this.options.id, which))
                    } else {
                        valStr = String(value)
                    }

                    if (
                        label.rzsv === undefined ||
                        label.rzsv.length !== valStr.length ||
                        (label.rzsv.length > 0 && label.rzsd === 0)
                    ) {
                        getDimension = true
                        label.rzsv = valStr
                    }

                    if (!noLabelInjection) {
                        label.html(valStr)
                    }
                    this.scope[which + 'Label'] = valStr

                    // Update width only when length of the label have changed
                    if (getDimension) {
                        this.getDimension(label)
                    }
                },

                /**
                 * Set maximum and minimum values for the slider and ensure the model and high
                 * value match these limits
                 * @returns {undefined}
                 */
                setMinAndMax: function () {
                    this.step = +this.options.step
                    this.precision = +this.options.precision

                    this.minValue = this.options.floor
                    if (this.options.logScale && this.minValue === 0)
                        throw Error("Can't use floor=0 with logarithmic scale")

                    if (this.options.enforceStep) {
                        this.lowValue = this.roundStep(this.lowValue)
                        if (this.range) this.highValue = this.roundStep(this.highValue)
                    }

                    if (this.options.ceil != null) this.maxValue = this.options.ceil
                    else
                        this.maxValue = this.options.ceil = this.range
                            ? this.highValue
                            : this.lowValue

                    if (this.options.enforceRange) {
                        this.lowValue = this.sanitizeValue(this.lowValue)
                        if (this.range) this.highValue = this.sanitizeValue(this.highValue)
                    }

                    this.applyLowValue()
                    if (this.range) this.applyHighValue()

                    this.valueRange = this.maxValue - this.minValue
                },

                /**
                 * Adds accessibility attributes
                 *
                 * Run only once during initialization
                 *
                 * @returns {undefined}
                 */
                addAccessibility: function () {
                    this.minH.attr('role', 'slider')
                    this.updateAriaAttributes()
                    if (
                        this.options.keyboardSupport &&
                        !(this.options.readOnly || this.options.disabled)
                    )
                        this.minH.attr('tabindex', '58')
                    else this.minH.attr('tabindex', '58')
                    if (this.options.vertical)
                        this.minH.attr('aria-orientation', 'vertical')
                    if (this.options.ariaLabel)
                        this.minH.attr('aria-label', this.options.ariaLabel)
                    else if (this.options.ariaLabelledBy)
                        this.minH.attr('aria-labelledby', this.options.ariaLabelledBy)

                    if (this.range) {
                        this.maxH.attr('role', 'slider')
                        if (
                            this.options.keyboardSupport &&
                            !(this.options.readOnly || this.options.disabled)
                        )
                            this.maxH.attr('tabindex', '58')
                        else this.maxH.attr('tabindex', '58')
                        if (this.options.vertical)
                            this.maxH.attr('aria-orientation', 'vertical')
                        if (this.options.ariaLabelHigh)
                            this.maxH.attr('aria-label', this.options.ariaLabelHigh)
                        else if (this.options.ariaLabelledByHigh)
                            this.maxH.attr('aria-labelledby', this.options.ariaLabelledByHigh)
                    }
                },

                /**
                 * Updates aria attributes according to current values
                 */
                updateAriaAttributes: function () {
                    this.minH.attr({
                        'aria-valuenow': this.scope.rzSliderModel,
                        'aria-valuetext': this.customTrFn(
                            this.scope.rzSliderModel,
                            this.options.id,
                            'model'
                        ),
                        'aria-valuemin': this.minValue,
                        'aria-valuemax': this.maxValue,
                    })
                    if (this.range) {
                        this.maxH.attr({
                            'aria-valuenow': this.scope.rzSliderHigh,
                            'aria-valuetext': this.customTrFn(
                                this.scope.rzSliderHigh,
                                this.options.id,
                                'high'
                            ),
                            'aria-valuemin': this.minValue,
                            'aria-valuemax': this.maxValue,
                        })
                    }
                },

                /**
                 * Calculate dimensions that are dependent on view port size
                 *
                 * Run once during initialization and every time view port changes size.
                 *
                 * @returns {undefined}
                 */
                calcViewDimensions: function () {
                    var handleWidth = this.getDimension(this.minH)

                    this.handleHalfDim = handleWidth / 2
                    this.barDimension = this.getDimension(this.fullBar)

                    this.maxPos = this.barDimension - handleWidth

                    this.getDimension(this.sliderElem)
                    this.sliderElem.rzsp = this.sliderElem[0].getBoundingClientRect()[
                        this.positionProperty
                    ]

                    if (this.initHasRun) {
                        this.updateFloorLab()
                        this.updateCeilLab()
                        this.initHandles()
                        var self = this
                        $timeout(function () {
                            self.updateTicksScale()
                        })
                    }
                },

                /**
                 * Update the ticks position
                 *
                 * @returns {undefined}
                 */
                updateTicksScale: function () {
                    if (!this.options.showTicks) return

                    var ticksArray = this.options.ticksArray || this.getTicksArray(),
                        translate = this.options.vertical ? 'translateY' : 'translateX',
                        self = this

                    if (this.options.rightToLeft) ticksArray.reverse()

                    this.scope.ticks = ticksArray.map(function (value) {
                        var position = self.valueToPosition(value)

                        if (self.options.vertical) position = self.maxPos - position

                        var translation = translate + '(' + Math.round(position) + 'px)'
                        var tick = {
                            selected: self.isTickSelected(value),
                            style: {
                                '-webkit-transform': translation,
                                '-moz-transform': translation,
                                '-o-transform': translation,
                                '-ms-transform': translation,
                                transform: translation,
                            },
                        }
                        if (tick.selected && self.options.getSelectionBarColor) {
                            tick.style['background-color'] = self.getSelectionBarColor()
                        }
                        if (!tick.selected && self.options.getTickColor) {
                            tick.style['background-color'] = self.getTickColor(value)
                        }
                        if (self.options.ticksTooltip) {
                            tick.tooltip = self.options.ticksTooltip(value)
                            tick.tooltipPlacement = self.options.vertical ? 'right' : 'top'
                        }
                        if (
                            self.options.showTicksValues === true ||
                            value % self.options.showTicksValues === 0
                        ) {
                            tick.value = self.getDisplayValue(value, 'tick-value')
                            if (self.options.ticksValuesTooltip) {
                                tick.valueTooltip = self.options.ticksValuesTooltip(value)
                                tick.valueTooltipPlacement = self.options.vertical
                                    ? 'right'
                                    : 'top'
                            }
                        }
                        if (self.getLegend) {
                            var legend = self.getLegend(value, self.options.id)
                            if (legend) tick.legend = legend
                        }
                        return tick
                    })
                },

                getTicksArray: function () {
                    var step = this.step,
                        ticksArray = []
                    if (this.intermediateTicks) step = this.options.showTicks
                    for (
                        var value = this.minValue;
                        value <= this.maxValue;
                        value += step
                    ) {
                        ticksArray.push(value)
                    }
                    return ticksArray
                },

                isTickSelected: function (value) {
                    if (!this.range) {
                        if (this.options.showSelectionBarFromValue !== null) {
                            var center = this.options.showSelectionBarFromValue
                            if (
                                this.lowValue > center &&
                                value >= center &&
                                value <= this.lowValue
                            )
                                return true
                            else if (
                                this.lowValue < center &&
                                value <= center &&
                                value >= this.lowValue
                            )
                                return true
                        } else if (this.options.showSelectionBarEnd) {
                            if (value >= this.lowValue) return true
                        } else if (this.options.showSelectionBar && value <= this.lowValue)
                            return true
                    }
                    if (this.range && value >= this.lowValue && value <= this.highValue)
                        return true
                    return false
                },

                /**
                 * Update position of the floor label
                 *
                 * @returns {undefined}
                 */
                updateFloorLab: function () {
                    this.translateFn(this.minValue, this.flrLab, 'floor')
                    this.getDimension(this.flrLab)
                    var position = this.options.rightToLeft
                        ? this.barDimension - this.flrLab.rzsd
                        : 0
                    this.setPosition(this.flrLab, position)
                },

                /**
                 * Update position of the ceiling label
                 *
                 * @returns {undefined}
                 */
                updateCeilLab: function () {
                    this.translateFn(this.maxValue, this.ceilLab, 'ceil')
                    this.getDimension(this.ceilLab)
                    var position = this.options.rightToLeft
                        ? 0
                        : this.barDimension - this.ceilLab.rzsd
                    this.setPosition(this.ceilLab, position)
                },

                /**
                 * Update slider handles and label positions
                 *
                 * @param {string} which
                 * @param {number} newPos
                 */
                updateHandles: function (which, newPos) {
                    if (which === 'lowValue') this.updateLowHandle(newPos)
                    else this.updateHighHandle(newPos)

                    this.updateSelectionBar()
                    this.updateTicksScale()
                    if (this.range) this.updateCmbLabel()
                },

                /**
                 * Helper function to work out the position for handle labels depending on RTL or not
                 *
                 * @param {string} labelName maxLab or minLab
                 * @param newPos
                 *
                 * @returns {number}
                 */
                getHandleLabelPos: function (labelName, newPos) {
                    var labelRzsd = this[labelName].rzsd,
                        nearHandlePos = newPos - labelRzsd / 2 + this.handleHalfDim,
                        endOfBarPos = this.barDimension - labelRzsd

                    if (!this.options.boundPointerLabels) return nearHandlePos

                    if (
                        (this.options.rightToLeft && labelName === 'minLab') ||
                        (!this.options.rightToLeft && labelName === 'maxLab')
                    ) {
                        return Math.min(nearHandlePos, endOfBarPos)
                    } else {
                        return Math.min(Math.max(nearHandlePos, 0), endOfBarPos)
                    }
                },

                /**
                 * Update low slider handle position and label
                 *
                 * @param {number} newPos
                 * @returns {undefined}
                 */
                updateLowHandle: function (newPos) {
                    this.setPosition(this.minH, newPos)
                    this.translateFn(this.lowValue, this.minLab, 'model')
                    this.setPosition(
                        this.minLab,
                        this.getHandleLabelPos('minLab', newPos)
                    )

                    if (this.options.getPointerColor) {
                        var pointercolor = this.getPointerColor('min')
                        this.scope.minPointerStyle = {
                            backgroundColor: pointercolor,
                        }
                    }

                    if (this.options.autoHideLimitLabels) {
                        this.shFloorCeil()
                    }
                },

                /**
                 * Update high slider handle position and label
                 *
                 * @param {number} newPos
                 * @returns {undefined}
                 */
                updateHighHandle: function (newPos) {
                    this.setPosition(this.maxH, newPos)
                    this.translateFn(this.highValue, this.maxLab, 'high')
                    this.setPosition(
                        this.maxLab,
                        this.getHandleLabelPos('maxLab', newPos)
                    )

                    if (this.options.getPointerColor) {
                        var pointercolor = this.getPointerColor('max')
                        this.scope.maxPointerStyle = {
                            backgroundColor: pointercolor,
                        }
                    }
                    if (this.options.autoHideLimitLabels) {
                        this.shFloorCeil()
                    }
                },

                /**
                 * Show/hide floor/ceiling label
                 *
                 * @returns {undefined}
                 */
                shFloorCeil: function () {
                    // Show based only on hideLimitLabels if pointer labels are hidden
                    if (this.options.hidePointerLabels) {
                        return
                    }
                    var flHidden = false,
                        clHidden = false,
                        isMinLabAtFloor = this.isLabelBelowFloorLab(this.minLab),
                        isMinLabAtCeil = this.isLabelAboveCeilLab(this.minLab),
                        isMaxLabAtCeil = this.isLabelAboveCeilLab(this.maxLab),
                        isCmbLabAtFloor = this.isLabelBelowFloorLab(this.cmbLab),
                        isCmbLabAtCeil = this.isLabelAboveCeilLab(this.cmbLab)

                    if (isMinLabAtFloor) {
                        flHidden = true
                        this.hideEl(this.flrLab)
                    } else {
                        flHidden = false
                        this.showEl(this.flrLab)
                    }

                    if (isMinLabAtCeil) {
                        clHidden = true
                        this.hideEl(this.ceilLab)
                    } else {
                        clHidden = false
                        this.showEl(this.ceilLab)
                    }

                    if (this.range) {
                        var hideCeil = this.cmbLabelShown ? isCmbLabAtCeil : isMaxLabAtCeil
                        var hideFloor = this.cmbLabelShown
                            ? isCmbLabAtFloor
                            : isMinLabAtFloor

                        if (hideCeil) {
                            this.hideEl(this.ceilLab)
                        } else if (!clHidden) {
                            this.showEl(this.ceilLab)
                        }

                        // Hide or show floor label
                        if (hideFloor) {
                            this.hideEl(this.flrLab)
                        } else if (!flHidden) {
                            this.showEl(this.flrLab)
                        }
                    }
                },

                isLabelBelowFloorLab: function (label) {
                    var isRTL = this.options.rightToLeft,
                        pos = label.rzsp,
                        dim = label.rzsd,
                        floorPos = this.flrLab.rzsp,
                        floorDim = this.flrLab.rzsd
                    return isRTL
                        ? pos + dim >= floorPos - 2
                        : pos <= floorPos + floorDim + 2
                },

                isLabelAboveCeilLab: function (label) {
                    var isRTL = this.options.rightToLeft,
                        pos = label.rzsp,
                        dim = label.rzsd,
                        ceilPos = this.ceilLab.rzsp,
                        ceilDim = this.ceilLab.rzsd
                    return isRTL ? pos <= ceilPos + ceilDim + 2 : pos + dim >= ceilPos - 2
                },

                /**
                 * Update slider selection bar, combined label and range label
                 *
                 * @returns {undefined}
                 */
                updateSelectionBar: function () {
                    var position = 0,
                        dimension = 0,
                        isSelectionBarFromRight = this.options.rightToLeft
                            ? !this.options.showSelectionBarEnd
                            : this.options.showSelectionBarEnd,
                        positionForRange = this.options.rightToLeft
                            ? this.maxH.rzsp + this.handleHalfDim
                            : this.minH.rzsp + this.handleHalfDim

                    if (this.range) {
                        dimension = Math.abs(this.maxH.rzsp - this.minH.rzsp)
                        position = positionForRange
                    } else {
                        if (this.options.showSelectionBarFromValue !== null) {
                            var center = this.options.showSelectionBarFromValue,
                                centerPosition = this.valueToPosition(center),
                                isModelGreaterThanCenter = this.options.rightToLeft
                                    ? this.lowValue <= center
                                    : this.lowValue > center
                            if (isModelGreaterThanCenter) {
                                dimension = this.minH.rzsp - centerPosition
                                position = centerPosition + this.handleHalfDim
                            } else {
                                dimension = centerPosition - this.minH.rzsp
                                position = this.minH.rzsp + this.handleHalfDim
                            }
                        } else if (isSelectionBarFromRight) {
                            dimension =
                                Math.abs(this.maxPos - this.minH.rzsp) + this.handleHalfDim
                            position = this.minH.rzsp + this.handleHalfDim
                        } else {
                            dimension = this.minH.rzsp + this.handleHalfDim
                            position = 0
                        }
                    }
                    this.setDimension(this.selBar, dimension)
                    this.setPosition(this.selBar, position)
                    if (this.range && this.options.showOuterSelectionBars) {
                        if (this.options.rightToLeft) {
                            this.setDimension(this.rightOutSelBar, position)
                            this.setPosition(this.rightOutSelBar, 0)
                            this.setDimension(
                                this.leftOutSelBar,
                                this.getDimension(this.fullBar) - (position + dimension)
                            )
                            this.setPosition(this.leftOutSelBar, position + dimension)
                        } else {
                            this.setDimension(this.leftOutSelBar, position)
                            this.setPosition(this.leftOutSelBar, 0)
                            this.setDimension(
                                this.rightOutSelBar,
                                this.getDimension(this.fullBar) - (position + dimension)
                            )
                            this.setPosition(this.rightOutSelBar, position + dimension)
                        }
                    }
                    if (this.options.getSelectionBarColor) {
                        var color = this.getSelectionBarColor()
                        this.scope.barStyle = {
                            backgroundColor: color,
                        }
                    } else if (this.options.selectionBarGradient) {
                        var offset =
                            this.options.showSelectionBarFromValue !== null
                                ? this.valueToPosition(this.options.showSelectionBarFromValue)
                                : 0,
                            reversed = (offset - position > 0) ^ isSelectionBarFromRight,
                            direction = this.options.vertical
                                ? reversed ? 'bottom' : 'top'
                                : reversed ? 'left' : 'right'
                        this.scope.barStyle = {
                            backgroundImage:
                            'linear-gradient(to ' +
                            direction +
                            ', ' +
                            this.options.selectionBarGradient.from +
                            ' 0%,' +
                            this.options.selectionBarGradient.to +
                            ' 100%)',
                        }
                        if (this.options.vertical) {
                            this.scope.barStyle.backgroundPosition =
                                'center ' +
                                (offset +
                                    dimension +
                                    position +
                                    (reversed ? -this.handleHalfDim : 0)) +
                                'px'
                            this.scope.barStyle.backgroundSize =
                                '100% ' + (this.barDimension - this.handleHalfDim) + 'px'
                        } else {
                            this.scope.barStyle.backgroundPosition =
                                offset -
                                position +
                                (reversed ? this.handleHalfDim : 0) +
                                'px center'
                            this.scope.barStyle.backgroundSize =
                                this.barDimension - this.handleHalfDim + 'px 100%'
                        }
                    }
                },

                /**
                 * Wrapper around the getSelectionBarColor of the user to pass to
                 * correct parameters
                 */
                getSelectionBarColor: function () {
                    if (this.range)
                        return this.options.getSelectionBarColor(
                            this.scope.rzSliderModel,
                            this.scope.rzSliderHigh
                        )
                    return this.options.getSelectionBarColor(this.scope.rzSliderModel)
                },

                /**
                 * Wrapper around the getPointerColor of the user to pass to
                 * correct parameters
                 */
                getPointerColor: function (pointerType) {
                    if (pointerType === 'max') {
                        return this.options.getPointerColor(
                            this.scope.rzSliderHigh,
                            pointerType
                        )
                    }
                    return this.options.getPointerColor(
                        this.scope.rzSliderModel,
                        pointerType
                    )
                },

                /**
                 * Wrapper around the getTickColor of the user to pass to
                 * correct parameters
                 */
                getTickColor: function (value) {
                    return this.options.getTickColor(value)
                },

                /**
                 * Update combined label position and value
                 *
                 * @returns {undefined}
                 */
                updateCmbLabel: function () {
                    var isLabelOverlap = null
                    if (this.options.rightToLeft) {
                        isLabelOverlap =
                            this.minLab.rzsp - this.minLab.rzsd - 10 <= this.maxLab.rzsp
                    } else {
                        isLabelOverlap =
                            this.minLab.rzsp + this.minLab.rzsd + 10 >= this.maxLab.rzsp
                    }

                    if (isLabelOverlap) {
                        var lowTr = this.getDisplayValue(this.lowValue, 'model'),
                            highTr = this.getDisplayValue(this.highValue, 'high'),
                            labelVal = ''
                        if (this.options.mergeRangeLabelsIfSame && lowTr === highTr) {
                            labelVal = lowTr
                        } else {
                            labelVal = this.options.rightToLeft
                                ? highTr + this.options.labelOverlapSeparator + lowTr
                                : lowTr + this.options.labelOverlapSeparator + highTr
                        }

                        this.translateFn(labelVal, this.cmbLab, 'cmb', false)
                        var pos = this.options.boundPointerLabels
                            ? Math.min(
                                Math.max(
                                    this.selBar.rzsp +
                                    this.selBar.rzsd / 2 -
                                    this.cmbLab.rzsd / 2,
                                    0
                                ),
                                this.barDimension - this.cmbLab.rzsd
                            )
                            : this.selBar.rzsp + this.selBar.rzsd / 2 - this.cmbLab.rzsd / 2

                        this.setPosition(this.cmbLab, pos)
                        this.cmbLabelShown = true
                        this.hideEl(this.minLab)
                        this.hideEl(this.maxLab)
                        this.showEl(this.cmbLab)
                    } else {
                        this.cmbLabelShown = false
                        this.updateHighHandle(this.valueToPosition(this.highValue))
                        this.updateLowHandle(this.valueToPosition(this.lowValue))
                        this.showEl(this.maxLab)
                        this.showEl(this.minLab)
                        this.hideEl(this.cmbLab)
                    }
                    if (this.options.autoHideLimitLabels) {
                        this.shFloorCeil()
                    }
                },

                /**
                 * Return the translated value if a translate function is provided else the original value
                 * @param value
                 * @param which if it's min or max handle
                 * @returns {*}
                 */
                getDisplayValue: function (value, which) {
                    if (this.options.stepsArray && !this.options.bindIndexForStepsArray) {
                        value = this.getStepValue(value)
                    }
                    return this.customTrFn(value, this.options.id, which)
                },

                /**
                 * Round value to step and precision based on minValue
                 *
                 * @param {number} value
                 * @param {number} customStep a custom step to override the defined step
                 * @returns {number}
                 */
                roundStep: function (value, customStep) {
                    var step = customStep ? customStep : this.step,
                        steppedDifference = parseFloat(
                            (value - this.minValue) / step
                        ).toPrecision(12)
                    steppedDifference = Math.round(+steppedDifference) * step
                    var newValue = (this.minValue + steppedDifference).toFixed(
                        this.precision
                    )
                    return +newValue
                },

                /**
                 * Hide element
                 *
                 * @param element
                 * @returns {jqLite} The jqLite wrapped DOM element
                 */
                hideEl: function (element) {
                    return element.css({
                        visibility: 'hidden',
                    })
                },

                /**
                 * Show element
                 *
                 * @param element The jqLite wrapped DOM element
                 * @returns {jqLite} The jqLite
                 */
                showEl: function (element) {
                    if (!!element.rzAlwaysHide) {
                        return element
                    }

                    return element.css({
                        visibility: 'visible',
                    })
                },

                /**
                 * Set element left/top position depending on whether slider is horizontal or vertical
                 *
                 * @param {jqLite} elem The jqLite wrapped DOM element
                 * @param {number} pos
                 * @returns {number}
                 */
                setPosition: function (elem, pos) {
                    elem.rzsp = pos
                    var css = {}
                    css[this.positionProperty] = Math.round(pos) + 'px'
                    elem.css(css)
                    return pos
                },

                /**
                 * Get element width/height depending on whether slider is horizontal or vertical
                 *
                 * @param {jqLite} elem The jqLite wrapped DOM element
                 * @returns {number}
                 */
                getDimension: function (elem) {
                    var val = elem[0].getBoundingClientRect()
                    if (this.options.vertical)
                        elem.rzsd = (val.bottom - val.top) * this.options.scale
                    else elem.rzsd = (val.right - val.left) * this.options.scale
                    return elem.rzsd
                },

                /**
                 * Set element width/height depending on whether slider is horizontal or vertical
                 *
                 * @param {jqLite} elem  The jqLite wrapped DOM element
                 * @param {number} dim
                 * @returns {number}
                 */
                setDimension: function (elem, dim) {
                    elem.rzsd = dim
                    var css = {}
                    css[this.dimensionProperty] = Math.round(dim) + 'px'
                    elem.css(css)
                    return dim
                },

                /**
                 * Returns a value that is within slider range
                 *
                 * @param {number} val
                 * @returns {number}
                 */
                sanitizeValue: function (val) {
                    return Math.min(Math.max(val, this.minValue), this.maxValue)
                },

                /**
                 * Translate value to pixel position
                 *
                 * @param {number} val
                 * @returns {number}
                 */
                valueToPosition: function (val) {
                    var fn = this.linearValueToPosition
                    if (this.options.customValueToPosition)
                        fn = this.options.customValueToPosition
                    else if (this.options.logScale) fn = this.logValueToPosition

                    val = this.sanitizeValue(val)
                    var percent = fn(val, this.minValue, this.maxValue) || 0
                    if (this.options.rightToLeft) percent = 1 - percent
                    return percent * this.maxPos
                },

                linearValueToPosition: function (val, minVal, maxVal) {
                    var range = maxVal - minVal
                    return (val - minVal) / range
                },

                logValueToPosition: function (val, minVal, maxVal) {
                    val = Math.log(val)
                    minVal = Math.log(minVal)
                    maxVal = Math.log(maxVal)
                    var range = maxVal - minVal
                    return (val - minVal) / range
                },

                /**
                 * Translate position to model value
                 *
                 * @param {number} position
                 * @returns {number}
                 */
                positionToValue: function (position) {
                    var percent = position / this.maxPos
                    if (this.options.rightToLeft) percent = 1 - percent
                    var fn = this.linearPositionToValue
                    if (this.options.customPositionToValue)
                        fn = this.options.customPositionToValue
                    else if (this.options.logScale) fn = this.logPositionToValue
                    return fn(percent, this.minValue, this.maxValue) || 0
                },

                linearPositionToValue: function (percent, minVal, maxVal) {
                    return percent * (maxVal - minVal) + minVal
                },

                logPositionToValue: function (percent, minVal, maxVal) {
                    minVal = Math.log(minVal)
                    maxVal = Math.log(maxVal)
                    var value = percent * (maxVal - minVal) + minVal
                    return Math.exp(value)
                },

                getEventAttr: function (event, attr) {
                    return event.originalEvent === undefined
                        ? event[attr]
                        : event.originalEvent[attr]
                },

                // Events
                /**
                 * Get the X-coordinate or Y-coordinate of an event
                 *
                 * @param {Object} event  The event
                 * @param targetTouchId The identifier of the touch with the X/Y coordinates
                 * @returns {number}
                 */
                getEventXY: function (event, targetTouchId) {
                    /* http://stackoverflow.com/a/12336075/282882 */
                    //noinspection JSLint
                    var clientXY = this.options.vertical ? 'clientY' : 'clientX'
                    if (event[clientXY] !== undefined) {
                        return event[clientXY]
                    }

                    var touches = this.getEventAttr(event, 'touches')

                    if (targetTouchId !== undefined) {
                        for (var i = 0; i < touches.length; i++) {
                            if (touches[i].identifier === targetTouchId) {
                                return touches[i][clientXY]
                            }
                        }
                    }

                    // If no target touch or the target touch was not found in the event
                    // returns the coordinates of the first touch
                    return touches[0][clientXY]
                },

                /**
                 * Compute the event position depending on whether the slider is horizontal or vertical
                 * @param event
                 * @param targetTouchId If targetTouchId is provided it will be considered the position of that
                 * @returns {number}
                 */
                getEventPosition: function (event, targetTouchId) {
                    var sliderPos = this.sliderElem.rzsp,
                        eventPos = 0
                    if (this.options.vertical)
                        eventPos = -this.getEventXY(event, targetTouchId) + sliderPos
                    else eventPos = this.getEventXY(event, targetTouchId) - sliderPos
                    return eventPos * this.options.scale - this.handleHalfDim // #346 handleHalfDim is already scaled
                },

                /**
                 * Get event names for move and event end
                 *
                 * @param {Event}    event    The event
                 *
                 * @return {{moveEvent: string, endEvent: string}}
                 */
                getEventNames: function (event) {
                    var eventNames = {
                        moveEvent: '',
                        endEvent: '',
                    }

                    if (this.getEventAttr(event, 'touches')) {
                        eventNames.moveEvent = 'touchmove'
                        eventNames.endEvent = 'touchend'
                    } else {
                        eventNames.moveEvent = 'mousemove'
                        eventNames.endEvent = 'mouseup'
                    }

                    return eventNames
                },

                /**
                 * Get the handle closest to an event.
                 *
                 * @param event {Event} The event
                 * @returns {jqLite} The handle closest to the event.
                 */
                getNearestHandle: function (event) {
                    if (!this.range) {
                        return this.minH
                    }
                    var position = this.getEventPosition(event),
                        distanceMin = Math.abs(position - this.minH.rzsp),
                        distanceMax = Math.abs(position - this.maxH.rzsp)
                    if (distanceMin < distanceMax) return this.minH
                    else if (distanceMin > distanceMax) return this.maxH
                    else if (!this.options.rightToLeft)
                        //if event is at the same distance from min/max then if it's at left of minH, we return minH else maxH
                        return position < this.minH.rzsp ? this.minH : this.maxH
                    else
                        //reverse in rtl
                        return position > this.minH.rzsp ? this.minH : this.maxH
                },

                /**
                 * Wrapper function to focus an angular element
                 *
                 * @param el {AngularElement} the element to focus
                 */
                focusElement: function (el) {
                    var DOM_ELEMENT = 0
                    el[DOM_ELEMENT].focus()
                },

                /**
                 * Bind mouse and touch events to slider handles
                 *
                 * @returns {undefined}
                 */
                bindEvents: function () {
                    var barTracking, barStart, barMove

                    if (this.options.draggableRange) {
                        barTracking = 'rzSliderDrag'
                        barStart = this.onDragStart
                        barMove = this.onDragMove
                    } else {
                        barTracking = 'lowValue'
                        barStart = this.onStart
                        barMove = this.onMove
                    }

                    if (!this.options.onlyBindHandles) {
                        this.selBar.on(
                            'mousedown',
                            angular.bind(this, barStart, null, barTracking)
                        )
                        this.selBar.on(
                            'mousedown',
                            angular.bind(this, barMove, this.selBar)
                        )
                    }

                    if (this.options.draggableRangeOnly) {
                        this.minH.on(
                            'mousedown',
                            angular.bind(this, barStart, null, barTracking)
                        )
                        this.maxH.on(
                            'mousedown',
                            angular.bind(this, barStart, null, barTracking)
                        )
                    } else {
                        this.minH.on(
                            'mousedown',
                            angular.bind(this, this.onStart, this.minH, 'lowValue')
                        )
                        if (this.range) {
                            this.maxH.on(
                                'mousedown',
                                angular.bind(this, this.onStart, this.maxH, 'highValue')
                            )
                        }
                        if (!this.options.onlyBindHandles) {
                            this.fullBar.on(
                                'mousedown',
                                angular.bind(this, this.onStart, null, null)
                            )
                            this.fullBar.on(
                                'mousedown',
                                angular.bind(this, this.onMove, this.fullBar)
                            )
                            this.ticks.on(
                                'mousedown',
                                angular.bind(this, this.onStart, null, null)
                            )
                            this.ticks.on(
                                'mousedown',
                                angular.bind(this, this.onTickClick, this.ticks)
                            )
                        }
                    }

                    if (!this.options.onlyBindHandles) {
                        this.selBar.on(
                            'touchstart',
                            angular.bind(this, barStart, null, barTracking)
                        )
                        this.selBar.on(
                            'touchstart',
                            angular.bind(this, barMove, this.selBar)
                        )
                    }
                    if (this.options.draggableRangeOnly) {
                        this.minH.on(
                            'touchstart',
                            angular.bind(this, barStart, null, barTracking)
                        )
                        this.maxH.on(
                            'touchstart',
                            angular.bind(this, barStart, null, barTracking)
                        )
                    } else {
                        this.minH.on(
                            'touchstart',
                            angular.bind(this, this.onStart, this.minH, 'lowValue')
                        )
                        if (this.range) {
                            this.maxH.on(
                                'touchstart',
                                angular.bind(this, this.onStart, this.maxH, 'highValue')
                            )
                        }
                        if (!this.options.onlyBindHandles) {
                            this.fullBar.on(
                                'touchstart',
                                angular.bind(this, this.onStart, null, null)
                            )
                            this.fullBar.on(
                                'touchstart',
                                angular.bind(this, this.onMove, this.fullBar)
                            )
                            this.ticks.on(
                                'touchstart',
                                angular.bind(this, this.onStart, null, null)
                            )
                            this.ticks.on(
                                'touchstart',
                                angular.bind(this, this.onTickClick, this.ticks)
                            )
                        }
                    }

                    if (this.options.keyboardSupport) {
                        this.minH.on(
                            'focus',
                            angular.bind(this, this.onPointerFocus, this.minH, 'lowValue')
                        )
                        if (this.range) {
                            this.maxH.on(
                                'focus',
                                angular.bind(this, this.onPointerFocus, this.maxH, 'highValue')
                            )
                        }
                    }
                },

                /**
                 * Unbind mouse and touch events to slider handles
                 *
                 * @returns {undefined}
                 */
                unbindEvents: function () {
                    this.minH.off()
                    this.maxH.off()
                    this.fullBar.off()
                    this.selBar.off()
                    this.ticks.off()
                },

                /**
                 * onStart event handler
                 *
                 * @param {?Object} pointer The jqLite wrapped DOM element; if null, the closest handle is used
                 * @param {?string} ref     The name of the handle being changed; if null, the closest handle's value is modified
                 * @param {Event}   event   The event
                 * @returns {undefined}
                 */
                onStart: function (pointer, ref, event) {
                    var ehMove,
                        ehEnd,
                        eventNames = this.getEventNames(event)

                    event.preventDefault()

                    // We have to do this in case the HTML where the sliders are on
                    // have been animated into view.
                    this.calcViewDimensions()

                    if (pointer) {
                        this.tracking = ref
                    } else {
                        pointer = this.getNearestHandle(event)
                        this.tracking = pointer === this.minH ? 'lowValue' : 'highValue'
                    }

                    pointer.addClass('rz-active')

                    if (this.options.keyboardSupport) this.focusElement(pointer)

                    ehMove = angular.bind(
                        this,
                        this.dragging.active ? this.onDragMove : this.onMove,
                        pointer
                    )
                    ehEnd = angular.bind(this, this.onEnd, ehMove)

                    $document.on(eventNames.moveEvent, ehMove)
                    $document.on(eventNames.endEvent, ehEnd)
                    this.endHandlerToBeRemovedOnEnd = ehEnd

                    this.callOnStart()

                    var changedTouches = this.getEventAttr(event, 'changedTouches')
                    if (changedTouches) {
                        // Store the touch identifier
                        if (!this.touchId) {
                            this.isDragging = true
                            this.touchId = changedTouches[0].identifier
                        }
                    }
                },

                /**
                 * onMove event handler
                 *
                 * @param {jqLite} pointer
                 * @param {Event}  event The event
                 * @param {boolean}  fromTick if the event occured on a tick or not
                 * @returns {undefined}
                 */
                onMove: function (pointer, event, fromTick) {
                    var changedTouches = this.getEventAttr(event, 'changedTouches')
                    var touchForThisSlider
                    if (changedTouches) {
                        for (var i = 0; i < changedTouches.length; i++) {
                            if (changedTouches[i].identifier === this.touchId) {
                                touchForThisSlider = changedTouches[i]
                                break
                            }
                        }
                    }

                    if (changedTouches && !touchForThisSlider) {
                        return
                    }

                    var newPos = this.getEventPosition(
                        event,
                        touchForThisSlider ? touchForThisSlider.identifier : undefined
                    ),
                        newValue,
                        ceilValue = this.options.rightToLeft
                            ? this.minValue
                            : this.maxValue,
                        flrValue = this.options.rightToLeft ? this.maxValue : this.minValue

                    if (newPos <= 0) {
                        newValue = flrValue
                    } else if (newPos >= this.maxPos) {
                        newValue = ceilValue
                    } else {
                        newValue = this.positionToValue(newPos)
                        if (fromTick && angular.isNumber(this.options.showTicks))
                            newValue = this.roundStep(newValue, this.options.showTicks)
                        else newValue = this.roundStep(newValue)
                    }
                    this.positionTrackingHandle(newValue)
                },

                /**
                 * onEnd event handler
                 *
                 * @param {Event}    event    The event
                 * @param {Function} ehMove   The bound move event handler
                 * @returns {undefined}
                 */
                onEnd: function (ehMove, event) {
                    var changedTouches = this.getEventAttr(event, 'changedTouches')
                    if (changedTouches && changedTouches[0].identifier !== this.touchId) {
                        return
                    }
                    this.isDragging = false
                    this.touchId = null

                    if (!this.options.keyboardSupport) {
                        this.minH.removeClass('rz-active')
                        this.maxH.removeClass('rz-active')
                        this.tracking = ''
                    }
                    this.dragging.active = false

                    var eventName = this.getEventNames(event)
                    $document.off(eventName.moveEvent, ehMove)
                    $document.off(eventName.endEvent, this.endHandlerToBeRemovedOnEnd)
                    this.endHandlerToBeRemovedOnEnd = null
                    this.callOnEnd()
                },

                onTickClick: function (pointer, event) {
                    this.onMove(pointer, event, true)
                },

                onPointerFocus: function (pointer, ref) {
                    this.tracking = ref
                    pointer.one('blur', angular.bind(this, this.onPointerBlur, pointer))
                    pointer.on('keydown', angular.bind(this, this.onKeyboardEvent))
                    pointer.on('keyup', angular.bind(this, this.onKeyUp))
                    this.firstKeyDown = true
                    this.gotFocus = true
                    pointer.addClass('rz-active')

                    this.currentFocusElement = {
                        pointer: pointer,
                        ref: ref,
                    }
                },

                onKeyUp: function () {
                    this.firstKeyDown = true
                    if (this.gotFocus) this.gotFocus = false;
                    else
                        this.callOnEnd()
                    
                },

                onPointerBlur: function (pointer) {
                    pointer.off('keydown')
                    pointer.off('keyup')
                    pointer.removeClass('rz-active')
                    if (!this.isDragging) {
                        this.tracking = ''
                        this.currentFocusElement = null
                    }
                },

                /**
                 * Key actions helper function
                 *
                 * @param {number} currentValue value of the slider
                 *
                 * @returns {?Object} action value mappings
                 */
                getKeyActions: function (currentValue) {
                    var increaseStep = currentValue + this.step,
                        decreaseStep = currentValue - this.step,
                        increasePage = currentValue + this.valueRange / 10,
                        decreasePage = currentValue - this.valueRange / 10

                    if (this.options.reversedControls) {
                        increaseStep = currentValue - this.step
                        decreaseStep = currentValue + this.step
                        increasePage = currentValue - this.valueRange / 10
                        decreasePage = currentValue + this.valueRange / 10
                    }

                    //Left to right default actions
                    var actions = {
                        UP: increaseStep,
                        DOWN: decreaseStep,
                        LEFT: decreaseStep,
                        RIGHT: increaseStep,
                        PAGEUP: increasePage,
                        PAGEDOWN: decreasePage,
                        HOME: this.options.reversedControls ? this.maxValue : this.minValue,
                        END: this.options.reversedControls ? this.minValue : this.maxValue,
                    }
                    //right to left means swapping right and left arrows
                    if (this.options.rightToLeft) {
                        actions.LEFT = increaseStep
                        actions.RIGHT = decreaseStep
                        // right to left and vertical means we also swap up and down
                        if (this.options.vertical) {
                            actions.UP = decreaseStep
                            actions.DOWN = increaseStep
                        }
                    }
                    return actions
                },

                onKeyboardEvent: function (event) {
                    var currentValue = this[this.tracking],
                        keyCode = event.keyCode || event.which,
                        keys = {
                            38: 'UP',
                            40: 'DOWN',
                            37: 'LEFT',
                            39: 'RIGHT',
                            33: 'PAGEUP',
                            34: 'PAGEDOWN',
                            36: 'HOME',
                            35: 'END',
                        },
                        actions = this.getKeyActions(currentValue),
                        key = keys[keyCode],
                        action = actions[key]
                    if (action == null || this.tracking === '') return
                    event.preventDefault()

                    if (this.firstKeyDown) {
                        this.firstKeyDown = false
                        this.callOnStart()
                    }

                    var self = this
                    $timeout(function () {
                        var newValue = self.roundStep(self.sanitizeValue(action))
                        if (!self.options.draggableRangeOnly) {
                            self.positionTrackingHandle(newValue)
                        } else {
                            var difference = self.highValue - self.lowValue,
                                newMinValue,
                                newMaxValue
                            if (self.tracking === 'lowValue') {
                                newMinValue = newValue
                                newMaxValue = newValue + difference
                                if (newMaxValue > self.maxValue) {
                                    newMaxValue = self.maxValue
                                    newMinValue = newMaxValue - difference
                                }
                            } else {
                                newMaxValue = newValue
                                newMinValue = newValue - difference
                                if (newMinValue < self.minValue) {
                                    newMinValue = self.minValue
                                    newMaxValue = newMinValue + difference
                                }
                            }
                            self.positionTrackingBar(newMinValue, newMaxValue)
                        }
                    })
                },

                /**
                 * onDragStart event handler
                 *
                 * Handles dragging of the middle bar.
                 *
                 * @param {Object} pointer The jqLite wrapped DOM element
                 * @param {string} ref     One of the refLow, refHigh values
                 * @param {Event}  event   The event
                 * @returns {undefined}
                 */
                onDragStart: function (pointer, ref, event) {
                    var position = this.getEventPosition(event)
                    this.dragging = {
                        active: true,
                        value: this.positionToValue(position),
                        difference: this.highValue - this.lowValue,
                        lowLimit: this.options.rightToLeft
                            ? this.minH.rzsp - position
                            : position - this.minH.rzsp,
                        highLimit: this.options.rightToLeft
                            ? position - this.maxH.rzsp
                            : this.maxH.rzsp - position,
                    }

                    this.onStart(pointer, ref, event)
                },

                /**
                 * getValue helper function
                 *
                 * gets max or min value depending on whether the newPos is outOfBounds above or below the bar and rightToLeft
                 *
                 * @param {string} type 'max' || 'min' The value we are calculating
                 * @param {number} newPos  The new position
                 * @param {boolean} outOfBounds Is the new position above or below the max/min?
                 * @param {boolean} isAbove Is the new position above the bar if out of bounds?
                 *
                 * @returns {number}
                 */
                getValue: function (type, newPos, outOfBounds, isAbove) {
                    var isRTL = this.options.rightToLeft,
                        value = null

                    if (type === 'min') {
                        if (outOfBounds) {
                            if (isAbove) {
                                value = isRTL
                                    ? this.minValue
                                    : this.maxValue - this.dragging.difference
                            } else {
                                value = isRTL
                                    ? this.maxValue - this.dragging.difference
                                    : this.minValue
                            }
                        } else {
                            value = isRTL
                                ? this.positionToValue(newPos + this.dragging.lowLimit)
                                : this.positionToValue(newPos - this.dragging.lowLimit)
                        }
                    } else {
                        if (outOfBounds) {
                            if (isAbove) {
                                value = isRTL
                                    ? this.minValue + this.dragging.difference
                                    : this.maxValue
                            } else {
                                value = isRTL
                                    ? this.maxValue
                                    : this.minValue + this.dragging.difference
                            }
                        } else {
                            if (isRTL) {
                                value =
                                    this.positionToValue(newPos + this.dragging.lowLimit) +
                                    this.dragging.difference
                            } else {
                                value =
                                    this.positionToValue(newPos - this.dragging.lowLimit) +
                                    this.dragging.difference
                            }
                        }
                    }
                    return this.roundStep(value)
                },

                /**
                 * onDragMove event handler
                 *
                 * Handles dragging of the middle bar.
                 *
                 * @param {jqLite} pointer
                 * @param {Event}  event The event
                 * @returns {undefined}
                 */
                onDragMove: function (pointer, event) {
                    var newPos = this.getEventPosition(event),
                        newMinValue,
                        newMaxValue,
                        ceilLimit,
                        flrLimit,
                        isUnderFlrLimit,
                        isOverCeilLimit,
                        flrH,
                        ceilH

                    if (this.options.rightToLeft) {
                        ceilLimit = this.dragging.lowLimit
                        flrLimit = this.dragging.highLimit
                        flrH = this.maxH
                        ceilH = this.minH
                    } else {
                        ceilLimit = this.dragging.highLimit
                        flrLimit = this.dragging.lowLimit
                        flrH = this.minH
                        ceilH = this.maxH
                    }
                    isUnderFlrLimit = newPos <= flrLimit
                    isOverCeilLimit = newPos >= this.maxPos - ceilLimit

                    if (isUnderFlrLimit) {
                        if (flrH.rzsp === 0) return
                        newMinValue = this.getValue('min', newPos, true, false)
                        newMaxValue = this.getValue('max', newPos, true, false)
                    } else if (isOverCeilLimit) {
                        if (ceilH.rzsp === this.maxPos) return
                        newMaxValue = this.getValue('max', newPos, true, true)
                        newMinValue = this.getValue('min', newPos, true, true)
                    } else {
                        newMinValue = this.getValue('min', newPos, false)
                        newMaxValue = this.getValue('max', newPos, false)
                    }
                    this.positionTrackingBar(newMinValue, newMaxValue)
                },

                /**
                 * Set the new value and position for the entire bar
                 *
                 * @param {number} newMinValue   the new minimum value
                 * @param {number} newMaxValue   the new maximum value
                 */
                positionTrackingBar: function (newMinValue, newMaxValue) {
                    if (
                        this.options.minLimit != null &&
                        newMinValue < this.options.minLimit
                    ) {
                        newMinValue = this.options.minLimit
                        newMaxValue = newMinValue + this.dragging.difference
                    }
                    if (
                        this.options.maxLimit != null &&
                        newMaxValue > this.options.maxLimit
                    ) {
                        newMaxValue = this.options.maxLimit
                        newMinValue = newMaxValue - this.dragging.difference
                    }

                    this.lowValue = newMinValue
                    this.highValue = newMaxValue
                    this.applyLowValue()
                    if (this.range) this.applyHighValue()
                    this.applyModel(true)
                    this.updateHandles('lowValue', this.valueToPosition(newMinValue))
                    this.updateHandles('highValue', this.valueToPosition(newMaxValue))
                },

                /**
                 * Set the new value and position to the current tracking handle
                 *
                 * @param {number} newValue new model value
                 */
                positionTrackingHandle: function (newValue) {
                    var valueChanged = false
                    newValue = this.applyMinMaxLimit(newValue)
                    if (this.range) {
                        if (this.options.pushRange) {
                            newValue = this.applyPushRange(newValue)
                            valueChanged = true
                        } else {
                            if (this.options.noSwitching) {
                                if (this.tracking === 'lowValue' && newValue > this.highValue)
                                    newValue = this.applyMinMaxRange(this.highValue)
                                else if (
                                    this.tracking === 'highValue' &&
                                    newValue < this.lowValue
                                )
                                    newValue = this.applyMinMaxRange(this.lowValue)
                            }
                            newValue = this.applyMinMaxRange(newValue)
                            /* This is to check if we need to switch the min and max handles */
                            if (this.tracking === 'lowValue' && newValue > this.highValue) {
                                this.lowValue = this.highValue
                                this.applyLowValue()
                                this.applyModel()
                                this.updateHandles(this.tracking, this.maxH.rzsp)
                                this.updateAriaAttributes()
                                this.tracking = 'highValue'
                                this.minH.removeClass('rz-active')
                                this.maxH.addClass('rz-active')
                                if (this.options.keyboardSupport) this.focusElement(this.maxH)
                                valueChanged = true
                            } else if (
                                this.tracking === 'highValue' &&
                                newValue < this.lowValue
                            ) {
                                this.highValue = this.lowValue
                                this.applyHighValue()
                                this.applyModel()
                                this.updateHandles(this.tracking, this.minH.rzsp)
                                this.updateAriaAttributes()
                                this.tracking = 'lowValue'
                                this.maxH.removeClass('rz-active')
                                this.minH.addClass('rz-active')
                                if (this.options.keyboardSupport) this.focusElement(this.minH)
                                valueChanged = true
                            }
                        }
                    }

                    if (this[this.tracking] !== newValue) {
                        this[this.tracking] = newValue
                        if (this.tracking === 'lowValue') this.applyLowValue()
                        else this.applyHighValue()
                        this.applyModel()
                        this.updateHandles(this.tracking, this.valueToPosition(newValue))
                        this.updateAriaAttributes()
                        valueChanged = true
                    }

                    if (valueChanged) this.applyModel(true)
                },

                applyMinMaxLimit: function (newValue) {
                    if (this.options.minLimit != null && newValue < this.options.minLimit)
                        return this.options.minLimit
                    if (this.options.maxLimit != null && newValue > this.options.maxLimit)
                        return this.options.maxLimit
                    return newValue
                },

                applyMinMaxRange: function (newValue) {
                    var oppositeValue =
                        this.tracking === 'lowValue' ? this.highValue : this.lowValue,
                        difference = Math.abs(newValue - oppositeValue)
                    if (this.options.minRange != null) {
                        if (difference < this.options.minRange) {
                            if (this.tracking === 'lowValue')
                                return this.highValue - this.options.minRange
                            else return this.lowValue + this.options.minRange
                        }
                    }
                    if (this.options.maxRange != null) {
                        if (difference > this.options.maxRange) {
                            if (this.tracking === 'lowValue')
                                return this.highValue - this.options.maxRange
                            else return this.lowValue + this.options.maxRange
                        }
                    }
                    return newValue
                },

                applyPushRange: function (newValue) {
                    var difference =
                        this.tracking === 'lowValue'
                            ? this.highValue - newValue
                            : newValue - this.lowValue,
                        minRange =
                            this.options.minRange !== null
                                ? this.options.minRange
                                : this.options.step,
                        maxRange = this.options.maxRange
                    // if smaller than minRange
                    if (difference < minRange) {
                        if (this.tracking === 'lowValue') {
                            this.highValue = Math.min(newValue + minRange, this.maxValue)
                            newValue = this.highValue - minRange
                            this.applyHighValue()
                            this.updateHandles(
                                'highValue',
                                this.valueToPosition(this.highValue)
                            )
                        } else {
                            this.lowValue = Math.max(newValue - minRange, this.minValue)
                            newValue = this.lowValue + minRange
                            this.applyLowValue()
                            this.updateHandles(
                                'lowValue',
                                this.valueToPosition(this.lowValue)
                            )
                        }
                        this.updateAriaAttributes()
                    } else if (maxRange !== null && difference > maxRange) {
                        // if greater than maxRange
                        if (this.tracking === 'lowValue') {
                            this.highValue = newValue + maxRange
                            this.applyHighValue()
                            this.updateHandles(
                                'highValue',
                                this.valueToPosition(this.highValue)
                            )
                        } else {
                            this.lowValue = newValue - maxRange
                            this.applyLowValue()
                            this.updateHandles(
                                'lowValue',
                                this.valueToPosition(this.lowValue)
                            )
                        }
                        this.updateAriaAttributes()
                    }
                    return newValue
                },

                /**
                 * Apply the model values using scope.$apply.
                 * We wrap it with the internalChange flag to avoid the watchers to be called
                 */
                applyModel: function (callOnChange) {
                    this.internalChange = true
                    this.scope.$apply()
                    callOnChange && this.callOnChange()
                    this.internalChange = false
                },

                /**
                 * Call the onStart callback if defined
                 * The callback call is wrapped in a $evalAsync to ensure that its result will be applied to the scope.
                 *
                 * @returns {undefined}
                 */
                callOnStart: function () {
                    if (this.options.onStart) {
                        var self = this,
                            pointerType = this.tracking === 'lowValue' ? 'min' : 'max'
                        this.scope.$evalAsync(function () {
                            self.options.onStart(
                                self.options.id,
                                self.scope.rzSliderModel,
                                self.scope.rzSliderHigh,
                                pointerType
                            )
                        })
                    }
                },

                /**
                 * Call the onChange callback if defined
                 * The callback call is wrapped in a $evalAsync to ensure that its result will be applied to the scope.
                 *
                 * @returns {undefined}
                 */
                callOnChange: function () {
                    if (this.options.onChange) {
                        var self = this,
                            pointerType = this.tracking === 'lowValue' ? 'min' : 'max'
                        this.scope.$evalAsync(function () {
                            self.options.onChange(
                                self.options.id,
                                self.scope.rzSliderModel,
                                self.scope.rzSliderHigh,
                                pointerType
                            )
                        })
                    }
                },

                /**
                 * Call the onEnd callback if defined
                 * The callback call is wrapped in a $evalAsync to ensure that its result will be applied to the scope.
                 *
                 * @returns {undefined}
                 */
                callOnEnd: function () {
                    if (this.options.onEnd) {
                        var self = this,
                            pointerType = this.tracking === 'lowValue' ? 'min' : 'max'
                        this.scope.$evalAsync(function () {
                            self.options.onEnd(
                                self.options.id,
                                self.scope.rzSliderModel,
                                self.scope.rzSliderHigh,
                                pointerType
                            )
                        })
                    }
                    this.scope.$emit('slideEnded')
                },
            }

            return Slider
        }])
        .directive('rzslider', ['RzSlider', function (RzSlider) {
            'use strict'

            return {
                restrict: 'AE',
                replace: true,
                scope: {
                    rzSliderModel: '=?',
                    rzSliderHigh: '=?',
                    rzSliderOptions: '&?',
                    rzSliderTplUrl: '@',
                },

                /**
                 * Return template URL
                 *
                 * @param {jqLite} elem
                 * @param {Object} attrs
                 * @return {string}
                 */
                templateUrl: function (elem, attrs) {
                    //noinspection JSUnresolvedVariable
                    return attrs.rzSliderTplUrl || 'rzSliderTpl.html'
                },

                link: function (scope, elem) {
                    scope.slider = new RzSlider(scope, elem) //attach on scope so we can test it
                },
            }
        }])

    // IDE assist

    /**
     * @name ngScope
     *
     * @property {number} rzSliderModel
     * @property {number} rzSliderHigh
     * @property {Object} rzSliderOptions
     */

    /**
     * @name jqLite
     *
     * @property {number|undefined} rzsp rzslider label position position
     * @property {number|undefined} rzsd rzslider element dimension
     * @property {string|undefined} rzsv rzslider label value/text
     * @property {Function} css
     * @property {Function} text
     */

    /**
     * @name Event
     * @property {Array} touches
     * @property {Event} originalEvent
     */

    /**
     * @name ThrottleOptions
     *
     * @property {boolean} leading
     * @property {boolean} trailing
     */

    module.run(['$templateCache', function ($templateCache) {
        'use strict';

        $templateCache.put('rzSliderTpl.html',
            "<div class=rzslider><span class=\"rz-bar-wrapper rz-left-out-selection\"><span class=rz-bar></span></span> <span class=\"rz-bar-wrapper rz-right-out-selection\"><span class=rz-bar></span></span> <span class=rz-bar-wrapper><span class=rz-bar></span></span> <span class=rz-bar-wrapper><span class=\"rz-bar rz-selection\" ng-style=barStyle></span></span> <span class=\"rz-pointer rz-pointer-min\" ng-style=minPointerStyle></span> <span class=\"rz-pointer rz-pointer-max\" ng-style=maxPointerStyle></span> <span class=\"rz-bubble rz-limit rz-floor\"></span> <span class=\"rz-bubble rz-limit rz-ceil\"></span> <span class=\"rz-bubble rz-model-value\"></span> <span class=\"rz-bubble rz-model-high\"></span> <span class=rz-bubble></span><ul ng-show=showTicks class=rz-ticks><li ng-repeat=\"t in ticks track by $index\" class=rz-tick ng-class=\"{'rz-selected': t.selected}\" ng-style=t.style ng-attr-uib-tooltip=\"{{ t.tooltip }}\" ng-attr-tooltip-placement={{t.tooltipPlacement}} ng-attr-tooltip-append-to-body=\"{{ t.tooltip ? true : undefined}}\"><span ng-if=\"t.value != null\" class=rz-tick-value ng-attr-uib-tooltip=\"{{ t.valueTooltip }}\" ng-attr-tooltip-placement={{t.valueTooltipPlacement}}>{{ t.value }}</span> <span ng-if=\"t.legend != null\" class=rz-tick-legend>{{ t.legend }}</span></li></ul></div>"
        );

    }]);

    return module.name
})
    ;
angular.module('AutoSMART.Web.Common')
    .directive('scrollTrigger', ["$window", function ($window) {
        return {
            link: function (scope, element, attrs) {
                var offset = parseInt(attrs.threshold) || 0;
                var e = jQuery(element[0]);
                var doc = jQuery(document);
                angular.element(document).bind('scroll', function () {
                    if (doc.scrollTop() + $window.innerHeight + offset > e.offset().top) {
                        scope.$apply(attrs.scrollTrigger);
                    }
                });
            }
        };
    }]);

angular.module('AutoSMART.Web.Common')
    .directive('dealerRaterRecommend', function () {
        
        function dealerRecommend(recommendValue) {
            var htmlContent = '';
            if (recommendValue === undefined) return htmlContent;

            var workingValue = (recommendValue.toLowerCase() === 'true');

            var className = 'recommend-positive';
            var text = "yes";

            if (!workingValue) {
                className = 'recommend-negative';
                text = 'no';
            }

            htmlContent = '<span class="' + className + '">' + text + '</span>';
            return htmlContent;
        }

        return {
            scope: { recommendValue: '@' },
            link: function (scope, element) {
                var result = dealerRecommend(scope.recommendValue);
                element.html(result);
            }
        };
    });
'use strict';

(function (module) {
    module.service('NotificationService', [
        '$rootScope',
        function ($rootScope) {

            function subscribe(eventname, scope, callback) {
                var handler = $rootScope.$on(eventname, callback);
                scope.$on("$destroy", handler);
            };

            function publish(eventname, data) {
                $rootScope.$emit(eventname, data);
            };

            return {
                publish: publish,
                subscribe: subscribe
            }
        }
    ]);
})(angular.module('AutoSMART.Web.Common'));

'use strict';

(function (module) {
    module.service('ZipCodeService', ['$http', '$q', '$cookies', 'NotificationService', 'Config', function ($http, $q, $cookies, NotificationService, Config) {

        var mainUrl = window.location.protocol + '//' + window.location.host + '/';

        var location = {
            CurrentZipCode: "",
            Latitude: "",
            Longitude: ""
        };
        var cookieName = "user-zip";
        var locationApiUrlRoot = Config.SystemTypesApiUrl;

        var saveZipCode = function (zipCode) {
            location.CurrentZipCode = zipCode;
            $cookies.put(cookieName, zipCode);
            getUserSetLocation(true).then(function() {
                NotificationService.publish("saveZipCode", location);
            });
        };

        function getStates() {
            var deferred = $q.defer();

            var promise = $http({
                method: 'GET',
                url: locationApiUrlRoot + '/Location/GetStates',

                headers: {
                    'Content-Type': 'application/json',
                    'Accept': '*/*',
                    'Accept-Language': 'en-US,en;q=0.8'
                }
            }).then(function (data) {
                if (data.data) {
                    data = data.data;
                }
                deferred.resolve(data);
            }).catch(function (data) {
                deferred.reject(data);
            });
            return deferred.promise;
        };

        function getUserSetLocation(saveToCookie) {
            var deferred = $q.defer();

            var link = mainUrl + 'home/UserLocation/';

            var promise = $http({
                method: 'Post',
                url: link,
                headers: {
                    'Content-Type': 'application/json',
                    'Accept': '*/*',
                    'Accept-Language': 'en-US,en;q=0.8'
                }
            }).then(function (data) {
                if (data.data) {
                    data = data.data;
                }
                if ((location.CurrentZipCode != data.ZipCode) && saveToCookie) {
                    $cookies.put(cookieName, data.ZipCode);
                }
                location.CurrentZipCode = data.ZipCode;
                location.Latitude = data.Latitude;
                location.Longitude = data.Longitude;
                deferred.resolve(data);
            }).catch(function (data) {
                deferred.reject(data);
            });
            return deferred.promise;
        };

        var init = function () {
            return $q.all([
                 getUserSetLocation(false)
            ]);
        };

        return {
            Init: init,
            Location: location,
            SaveZipCode: saveZipCode,
            GetStates: getStates
        }
    }]);

})(angular.module('AutoSMART.Web.Common'));
'use strict';

(function (module) {
    module.service('AmplitudeService', ['Config', function (Config) {
        var initialized = false;
        var instance = undefined;

        function init() {
            window.amplitude.init(Config.AmplitudeApiKey, null, {
                includeUtm: true,
                includeReferrer: true
            });
            instance = window.amplitude.getInstance();
            initialized = true;
        }

        function identifyUser(userId, userProperties) {
            window.amplitude.setUserId(userId);
            window.amplitude.setUserProperties(userProperties);
        }

        function logEvent(eventName, params) {
            if (initialized === false || !instance) {
                init();
            }
            window.amplitude.logEvent(eventName, params);
        }

        function logEventWithGroups(eventName, params, group) {
            if (initialized === false || !instance) {
                init();
            }

            window.amplitude.logEventWithGroups(eventName, params, group);
        }

        return {
            init: init,
            logEvent: logEvent,
            logEventWithGroups: logEventWithGroups,
            identifyUser: identifyUser
        };

    }]);

})(angular.module('AutoSMART.Web.Common'));

'use strict';

(function (module) {
    module.service('BuildEstimateCalcService', ['$http', '$q', '$cookies', 'NotificationService', 'Config',
    function ($http, $q, $cookies, NotificationService, Config) {
        var affordabilityCalculator = JSON.parse(Config.AffordabilityCalculator.replace(/&quot;/g, '"'));
        var purchasePrice = null;
        var minMaxMonthlyPayment = {
            minRange:0,
            maxRange:0
        };
        var paymentCalculatorSettings = {
            AmountOwned: 0,
            CashRebate: 0,
            DownPayment: 0,
            LoanDuration: "12",
            SelectedCreditScoreId: "2",
            TradeIn: 0,
            maxApr: affordabilityCalculator.CreditScore[1].MaxRate,
            minApr: affordabilityCalculator.CreditScore[1].MinRate
        };

        var saveBuildEstimateCalc = function(payCalcSett) {
            $cookies.put("PaymentCalculatorSettings", JSON.stringify(payCalcSett));
            NotificationService.publish("updatedMinMaxMonthly", minMaxMonthlyPayment);
        };



        var getPaymentCalc = function() {
            var PaymentCalculatorSettings = $cookies.get("PaymentCalculatorSettings");
            if (PaymentCalculatorSettings != null) {
                paymentCalculatorSettings = JSON.parse(PaymentCalculatorSettings);
            }
            return paymentCalculatorSettings;
        };

        var getPurchasePrice = function() {
            var BuildSelection = $cookies.get("BuildSelection");
            if (BuildSelection) {
                var BuildSelectionParse = JSON.parse(BuildSelection);
                purchasePrice = (BuildSelectionParse.PurchasePrice) ? BuildSelectionParse.PurchasePrice : 0;
            }
            return purchasePrice;
        };

        var calcMonthlyPayment = function(apr, purchasePrice, cashrebate,downPayment,tradIn,amountOwned,loanDuration) {
            var PurchasePrice = purchasePrice ? purchasePrice : 0;
            var CashRebate = parseInt(cashrebate) ? parseInt(cashrebate) : 0;
            var DownPayment = downPayment ? downPayment : 0;
            var TradIn = tradIn ? tradIn : 0;
            var AmountOwed = parseInt(amountOwned) ? parseInt(amountOwned) : 0;

            var totalLoanAmount = PurchasePrice - CashRebate - DownPayment - TradIn + AmountOwed;

            if (apr == 0)
                apr = 0.1;
            var amin = (1 + apr / 1200);
            var xmin = Math.pow(amin, loanDuration);
            xmin = 1 / xmin;
            xmin = 1 - xmin;
            return (totalLoanAmount) * (apr / 1200) / xmin;
        };

        var init = function () {
            getPaymentCalc();
            getPurchasePrice();
        };

        return {
            Init: init,
            SaveBuildEstimateCalc: saveBuildEstimateCalc,
            getPurchasePrice: getPurchasePrice,
            getPaymentCalc: getPaymentCalc,
            CalcMonthlyPayment: calcMonthlyPayment,
        }
    }]);

})(angular.module('AutoSMART.Web.Common'));
'use strict';

(function (module) {
    module.service('ChromeDataService', ['$http', '$q', 'Config', function ($http, $q, Config) {

        var allIncentives = {
            StackabilityList: [],
            Incentives: []
        };

        var setupChromeIncentive = function (data) {
            var sList = [];
            angular.forEach(data.StackabilityList, function (value) {
                if (sList.indexOf(value.SignatureID) < 0)
                    sList.push(value.SignatureID);
            });
            allIncentives.StackabilityList = sList;

            var iList = [];
            angular.forEach(data.Incentives, function (value) {
                var expDate = new Date(value.ExpiryDate);
                var effDate = new Date(value.EffectiveDate);
                var today = new Date();

                if (value.Type.toLowerCase() === 'cash' && (effDate < today < expDate)) {
                    iList.push(value);
                }
            });
            allIncentives.Incentives = iList;
        }

        var getChromeIncentive = function(chromeStyleId, zipCode) {
            var deferred = $q.defer();
            var link = Config.BaseDirectory + "/ChromeDataService/GetCashIncentive?zipCode=" + zipCode + "&styleId=" + chromeStyleId;

            $http({
                method: 'GET',
                url: link
            })
            .then(function (result) {
                setupChromeIncentive(result);
                deferred.resolve(allIncentives);
            }, function(errorResult) {
                deferred.reject(errorResult);
            });
            return deferred.promise;
        };

        return {
            GetChromeIncentive: getChromeIncentive
        }
    }]);

})(angular.module('AutoSMART.Web.Common'));
'use strict';

(function (module) {
    module.service('VehicleMakesService', ['$http', '$q', '$location', 'Config', function ($http, $q, $location, Config) {

        var ctrl = this;
        var mainUrl = window.location.protocol + '//' + window.location.host + '/';
        
        var retrieveInventoryMakes = function () {
            var deferred = $q.defer();

            var promise = $http({
                method: 'GET',
                url: mainUrl + '/Search/GetMakes',

                headers: {
                    'Content-Type': 'application/json',
                    'Accept': '*/*',
                    'Accept-Language': 'en-US,en;q=0.8'

                },

            }).then(function (data) {
                if (data.data) {
                    data = data.data;
                }
                deferred.resolve(data);
            }).catch(function (data) {
                deferred.reject(data);
            });
            return deferred.promise;
        }






        return {
            GetInventoryMakes: retrieveInventoryMakes
        }
    }]);

})(angular.module('AutoSMART.Web.Common'));
'use strict';

(function (module) {
    module.service('VehicleModelService', ['$http', '$q', function ($http, $q) {

        var ctrl = this;


        var mainUrl = window.location.protocol + '//' + window.location.host + '/';
        
        var getInventoryModel = function (make) {
            var deferred = $q.defer();

            var promise = $http({
                method: 'GET',
                url: mainUrl + '/Search/GetModels/?Make=' + make,
                headers: {
                    'Content-Type': 'application/json',
                    'Accept': '*/*',
                    'Accept-Language': 'en-US,en;q=0.8'

                },
            }).then(function (data) {
                if (data.data) {
                    data = data.data;
                }
                deferred.resolve(data);
            }).catch(function (data) {
                deferred.reject(data);
            });
            return deferred.promise;
        };

        return {
            GetInventoryModel: getInventoryModel
        }
    }]);

})(angular.module('AutoSMART.Web.Common'));
'use strict';

(function (module) {
    module.service('DealerRaterService', ['$http', '$q', 'Config', function ($http, $q, Config) {

        var getRatings = function (clientCode, page, filterType) {
            if (!filterType)
                filterType = 0;
            if (!page)
                page = 0;
            var link = Config.BaseDirectory + "/ClientInfoService/RetrieveDealerRating/?clientCode=" + clientCode + "&page=" + page + "&filterType=" + filterType;

            var deferred = $q.defer();

            var promise = $http({
                method: 'Get',
                url: link,
                headers: {
                    'Content-Type': 'application/json',
                    'Accept': '*/*',
                    'Accept-Language': 'en-US,en;q=0.8'
                }
            }).then(function (data) {
                if (data.data) {
                    data = data.data;
                }
                deferred.resolve(data);
            }).catch(function (data) {
                deferred.reject(data);
            });
            return deferred.promise;
        };

        return {
            GetRatings: getRatings
        }
    }]);

})(angular.module('AutoSMART.Web.Common'));
'use strict';

(function (module) {
    module.service('LeadService', ['$http', '$q', 'Config', 'QuickQuoteService', function ($http, $q, Config, QuickQuoteService) {

        var lead = {
            WebUserCopyEmail: true,
            VehicleYear: '',
            MakeID: null,
            ModelID: null,
            VehicleTrim: '',
            VehicleStyle: '',
            VehiclePrice: 0,
            VIN: '',
            Usage: 0,
            SerialNumber: '',
            UsageType: '',
            ExteriorColor: '',
            InteriorColor: '',
            Doors: 0,
            Engine: '',
            TransmissionType: '',
            DriveTrain: '',
            FuelType: '',
            WebUserComments: null,
            FirstName: '',
            LastName: '',
            UserName: '',
            UserEmailAddress: '',
            UserPhone: '',
            UserZipCode: Config.ZipCode,
            Options: null,
            DealerComments: null,
            IsProcessed: true,
            ReferredToClientID: 0,
            CobrandClientID: Config.ClientID,
            ItemID: null,
            ItemTypeID: 0,
            ItemName: '',
            ReferralType: '',
            ChromeStyleID: 0,
            ClientWebsiteID: Config.ClientWebSiteID,
            LeadTypeID: 0,
            SourcePlatformID: 1,
            WebUserLanguageLocale: 'en-US',
            TradeIn: false,
            ReferralContactTypeID: 1, //Have the Dealer Contact Me
            CreditUnionWebsiteName: Config.ClientName,
            CreditUnionName: Config.OriginalClientName,
            VehicleCondition: '',
            VehicleType: '',
            DateEntered: new Date(),
            WebsiteTypeID: Config.WebsiteTypeID,
            IsFinanced: false,
            APRDuration: 0,
            APRMinimum: 0,
            APRMaximum: 0,
            APRMinimumPrice: 0,
            APRMaximumPrice: 0,
            VehicleImageURL: '',
            IsPreferredPlus: false,
            CertificateCode: null,
            CertificateId: null,
            ValidThrough: null,
            VehicleMake: '',
            VehicleModel: '',
            StockNumber: '',
            BuildTrim: '',
            BuildStyle: '',
            BuildExteriorColor: '',
            BuildInteriorColor: '',
            BuildOptions: '',
            BuildPaintScheme: '',
            BuildMSRPwOptions: null,
            BuildPurchasePrice: null,
            BuildMSRP: null,
            IsStockPhoto: false,
            DistanceToDealer: null,
            DealTypeId: null,
            EstimatedText: '',
            VehicleTradeInDetails: null,
            ProductSource: 'AutoSmart',
            ApplicationId: null,
            CreditScore: null,
            IsAS4QuickQuoteLead: false,
            DealershipName: null,
            ApprovedAmount: null,
            UseAS5EmailTemplate: false,
            DownPayment: 0,
            ContactReasonId: null,
            QuestionToDealer: null,
            DealerContactlessServices: null
        };
        var saveLead = function () {
            var link = Config.AutoBuyingApiUrl + "VehicleLead/";

            var deferred = $q.defer();
            
            $http({
                method: 'POST',
                url: link,
                data: '\'' + angular.toJson(lead).replace('\'', '\\\'') + '\'',
                headers: {
                    'Content-Type': 'application/json',
                    'Accept': '*/*',
                    'Accept-Language': 'en-US,en;q=0.8'
                }
            }).then(function (data) {
                if (data.data) {
                    data = data.data;
                }
                deferred.resolve(data);
            }).catch(function (data) {
                deferred.reject(data);
            });
            return deferred.promise;
        };

        var mapLeadContents = function (vehicleInfo) {

            if (vehicleInfo) {
                lead.ContactReasonId = vehicleInfo.ContactReasonId;
                lead.QuestionToDealer = vehicleInfo.QuestionToDealer;
                lead.DealerContactlessServices = '';

                angular.forEach(vehicleInfo.DealerContactlessServices, function (dcs) {
                  if (!lead.DealerContactlessServices)
                    lead.DealerContactlessServices += dcs;
                  else
                    lead.DealerContactlessServices += ';' + dcs;
                });

                lead.VehicleYear = (vehicleInfo.Year) ? vehicleInfo.Year : vehicleInfo.ItemYear;
                lead.VehicleMake = (vehicleInfo.MakeName) ? vehicleInfo.MakeName : '';
                lead.VehicleModel = (vehicleInfo.ModelName) ? vehicleInfo.ModelName : '';
                lead.VehicleTrim = vehicleInfo.Trim.replace(/[']/g, "").replace(/["]/g, "&quot;");
                if (vehicleInfo.Style) {
                    lead.VehicleStyle = vehicleInfo.Style.replace(/[']/g, "").replace(/["]/g, "&quot;");
                } else {
                    lead.VehicleStyle = '';
                }                

                lead.VehiclePrice = (vehicleInfo.ItemID) ? vehicleInfo.PriceRetail : vehicleInfo.BaseMSRP;
                lead.DealershipName = vehicleInfo.DealerName;
                lead.VIN = vehicleInfo.VIN;
                lead.Usage = vehicleInfo.Usage;
                lead.SerialNumber = vehicleInfo.SerialNumber;
                lead.UsageType = vehicleInfo.UsageType;
                lead.ExteriorColor = vehicleInfo.ExteriorColor;
                lead.InteriorColor = vehicleInfo.InteriorColor;
                lead.Doors = (vehicleInfo.Doors) ? vehicleInfo.Doors : null;
                lead.Engine = vehicleInfo.EngineType;
                lead.TransmissionType = (vehicleInfo.TransmissionType) ? encodeURIComponent(vehicleInfo.TransmissionType) : null;
                lead.DriveTrain = vehicleInfo.Drivetrain;
                lead.FuelType = vehicleInfo.FuelType;
                lead.UserName = vehicleInfo.UserName;
                lead.UserEmailAddress = vehicleInfo.UserEmailAddress;
                lead.UserPhone = vehicleInfo.UserPhone;
                lead.DealerComments = (vehicleInfo.DealerComments) ? encodeURIComponent(vehicleInfo.DealerComments).replace(/[']/g, escape).replace(/\*/g, "%2A") : null;
                lead.Options = (vehicleInfo.VehicleOptions) ? encodeURIComponent(vehicleInfo.VehicleOptions).replace(/[']/g, escape).replace(/\*/g, "%2A") : null;
                lead.ReferredToClientID = vehicleInfo.SellerInfo ? vehicleInfo.SellerInfo.ClientId : vehicleInfo.ClientId;
                lead.ItemID = vehicleInfo.ItemID;
                lead.ItemTypeID = (vehicleInfo.ItemTypeID) ? vehicleInfo.ItemTypeID : vehicleInfo.ItemTypeId;
                lead.VehicleType = (lead.ItemTypeID) ? getVehicleType(lead.ItemTypeID) : null;
                lead.ItemName = (vehicleInfo.ItemNameStyle) ? vehicleInfo.ItemNameStyle.replace(/[']/g, "").replace(/["]/g, "&quot;") : vehicleInfo.ItemName.replace(/[']/g, "").replace(/["]/g, "&quot;");
                lead.ReferralType = vehicleInfo.PhysicalCondition;
                lead.ChromeStyleID = vehicleInfo.ChromeStyleID;
                lead.VehicleCondition = vehicleInfo.PhysicalCondition;
                lead.IsFinanced = (vehicleInfo.IsFinanced) ? vehicleInfo.IsFinanced : false;
                lead.APRDuration = vehicleInfo.APRDuration;
                lead.APRMinimum = vehicleInfo.APRMinimum;
                lead.APRMaximum = vehicleInfo.APRMaximum;
                lead.APRMinimumPrice = vehicleInfo.APRMinimumPrice;
                lead.APRMaximumPrice = vehicleInfo.APRMaximumPrice;
                lead.DownPayment = vehicleInfo.DownPayment;
                var pqFormValues = QuickQuoteService.GetPreQualFormData();
                if (pqFormValues) {
                    lead.DownPayment = pqFormValues.DownPayment;
                }
                lead.VehicleImageURL = (vehicleInfo.ImagePath) ? vehicleInfo.ImagePath : ((vehicleInfo.Images && vehicleInfo.Images.length > 0) ? vehicleInfo.Images[0].ImagePath : '');
                lead.IsPreferredPlus = vehicleInfo.IsPreferredPlus;
                lead.LeadTypeID = getLeadType();
                lead.CertificateCode = (lead.IsPreferredPlus) ? Config.ConfirmationCode : null;
                lead.CertificateId = (lead.IsPreferredPlus) ? Config.ConfirmationCode : null;
                lead.MakeID = vehicleInfo.MakeID;
                lead.ModelID = vehicleInfo.ModelID;
                lead.StockNumber = vehicleInfo.StockNumber;
                if (vehicleInfo.hasOwnProperty("IsStockPhoto")) {
                    lead.IsStockPhoto = vehicleInfo.IsStockPhoto;
                } else if (vehicleInfo.Images) {
                    lead.IsStockPhoto = vehicleInfo.Images[0].IsStockPhoto;
                }

                if (vehicleInfo.BuildSelection) {
                    lead.BuildTrim = vehicleInfo.BuildSelection.Trim.replace(/[']/g, "").replace(/["]/g, "&quot;");
                    lead.BuildStyle = vehicleInfo.BuildSelection.Style.replace(/[']/g, "").replace(/["]/g, "&quot;");
                    lead.BuildExteriorColor = vehicleInfo.BuildSelection.ExteriorColor;
                    lead.BuildInteriorColor = vehicleInfo.BuildSelection.Interior;
                    lead.BuildOptions = encodeURIComponent(JSON.stringify(vehicleInfo.BuildSelection.Options));
                    lead.BuildPaintScheme = vehicleInfo.BuildSelection.PaintScheme;
                    lead.BuildMSRPwOptions = vehicleInfo.BuildSelection.MSRPwOptions;
                    lead.BuildPurchasePrice = vehicleInfo.BuildSelection.PurchasePrice;
                    lead.BuildExteriorColorPrice = vehicleInfo.BuildSelection.ExteriorColorPrice;
                    lead.BuildInteriorColorPrice = vehicleInfo.BuildSelection.InteriorPrice;
                    lead.BuildPaintSchemePrice = vehicleInfo.BuildSelection.PaintSchemePrice;
                    lead.BuildMSRP = vehicleInfo.BuildSelection.MSRP;
                }

                lead.DealTypeId = vehicleInfo.DealTypeId;

                if ('PricingData' in vehicleInfo) {
                    lead.EstimatedText = vehicleInfo.PricingData.PriceDifferenceText;
                    lead.DealTypeID = vehicleInfo.PricingData.DealTypeID;
                }
                if ('DistanceToDealer' in vehicleInfo) {
                    lead.DistanceToDealer = vehicleInfo.DistanceToDealer;
                } else if ('Distance' in vehicleInfo) {
                    lead.DistanceToDealer = vehicleInfo.Distance;
                } else {
                    lead.DistanceToDealer = 0;
                }


                var expireDate = new Date();
                expireDate.setDate(expireDate.getDate() + 7);
                lead.ValidThrough = (lead.IsPreferredPlus) ? expireDate : null;

                lead.VehicleTradeInDetails = vehicleInfo.VehicleTradeInDetails;

                var qQResultCookie = QuickQuoteService.GetQQResultCookie();                
                if (qQResultCookie) {
                    lead.ApplicationId = qQResultCookie.applicationId;
                    lead.CreditScore = qQResultCookie.borrower.ficov9Score;
                    lead.FirstName = qQResultCookie.borrower.firstName;
                    lead.LastName = qQResultCookie.borrower.lastName;
                    lead.IsAS4QuickQuoteLead = true;
                }

                var rate = QuickQuoteService.GetQQSelection();
                if (rate) {
                    lead.ApprovedAmount = rate.loanAmount;                    
                }

               
            }



            // Method to determine leadtypeid for lead object
            function getLeadType() {

                if (Config.IsFromLendingTree == "True") {
                    return 5;
                }
                if (vehicleInfo.IsPreferredPlus) {
                    return 4;
                }
                if (vehicleInfo.ItemID) {
                    return 2;
                }
                return 1;
            }

        };

        var getVehicleType = function (itemTypeId) {
            switch (itemTypeId) {
                case 5:
                    return "Cars and Light Trucks";
                case 6:
                    return "Motorcycles";
                case 7:
                    return "Powersports";
                case 8:
                    return "RVs and Trailers";
                case 9:
                    return "Boats";
                case 10:
                    return "Personal Watercraft";
                case 11:
                    return "Commercial Vehicles";
            }
        };

        return {
            SaveLead: saveLead,
            Lead: lead,
            MapLeadContents: mapLeadContents
        }
    }]);

})(angular.module('AutoSMART.Web.Common'));


(function (module) {
    module.service('VehicleTradeInService', ['$http', '$q', '$cookies', '$window', 'NotificationService', function ($http, $q, $cookies, $window, NotificationService) {

        var cookieName = 'kbbtradeinoutput';

        var data = {
            VehicleTradeInDetails: []
        };

        var init = function () {
            return $q.all([loadData(),]);
        };

        function loadData() {
            var deferred = $q.defer();
            
            if (data.VehicleTradeInDetails.length < 1) {
                setVehicleTradeInDetails();
            }

            deferred.resolve();
        };

        var getVehicleTradeInDetailsFromStorage = function () {
            return $.cookie(cookieName)
                ? $.cookie(cookieName)
                : localStorage.getItem(cookieName);
        };

        var setVehicleTradeInDetails = function () {

            var kbbOutput = getVehicleTradeInDetailsFromStorage();

            if (kbbOutput != null && kbbOutput != "") {
                kbbOutput = JSON.parse(kbbOutput);
                var outJson = {
                    "KBBVehicleId": kbbOutput.VehicleId,
                    "Year": kbbOutput.VehicleYear,
                    "MakeName": kbbOutput.VehicleMake,
                    "ModelName": kbbOutput.VeicleModel,
                    "Trim": kbbOutput.VehicleTrim,
                    "TradeInValue": kbbOutput.VehicleValue,
                    "AmountOwed": 0,
                    "Condition": kbbOutput.VehicleConditon,
                    "Mileage": kbbOutput.VehicleMileage,
                    "Options": "",
                    "Color": (kbbOutput.VehicleColor) ? kbbOutput.VehicleColor : ""
                };
                data.VehicleTradeInDetails.push(outJson);
            }
            else {
                data.VehicleTradeInDetails = [];
            }
        };

        return {
            Init: init,
            Data: data
        };

    }]);

})(angular.module('AutoSMART.Web.Common'));
'use strict';
(function (module) {
    module.service('VehiclePricingService', ['$filter', '$locale', function ($filter, $locale) {

        var getDealTypeText = function (dealTypeId) {
            switch (dealTypeId) {
                case 1:
                    return 'Great Price';
                case 2:
                    return 'Fair price';
                case 3:
                    return 'Above Average';
            }

            return '';
        };

        var getPriceDifference = function (priceRetail, marketPrice) {
            return (priceRetail - marketPrice);
        };

        var getPriceDifferencePercentage = function (priceDiff, marketPrice) {
            return priceDiff / marketPrice * 100;
        }

        var getPriceLabel = function (dealTypeId) {
            switch (dealTypeId) {
                case 1:
                    return 'great-price-label';
                case 2:
                    return 'fair-price-label';
                case 3:
                    return 'overpriced-label';
            }
            return '';
        };

        var getPriceSubLabel = function (dealTypeId) {
            switch (dealTypeId) {
                case 1:
                    return 'great-price-sub-label';
                case 3:
                    return 'overpriced-sub-label';
            }
            return 'fair-price-sub-label';
        };

        var getPriceStyle = function (dealTypeId) {
            switch (dealTypeId) {
                case 1:
                    return 'great-price-price';
                case 2:
                    return 'fair-price-price';
                case 3:
                    return 'overpriced-price';
            }
            return '';
        };

        var getFairPriceArrow = function (priceDifference, dealTypeId) {
            if (dealTypeId !== 2) {
                return '';
            }

            var arrowClass = 'market-price-fair-up';
            if (priceDifference < 0) {
                arrowClass = 'market-price-fair-down';
            }
            return arrowClass;
        }

        var getPriceArrow = function (priceDifference, dealTypeId) {
            switch (dealTypeId) {
                case 1:
                    if (priceDifference < 0) {
                        return 'market-price-great-down';
                    }
                    return 'market-price-great-up';
                case 2:
                    if (priceDifference < 0) {
                        return 'market-price-fair-down';
                    }
                    return 'market-price-fair-up';
                case 3:
                    if (priceDifference < 0) {
                        return 'market-price-overpriced-down';
                    }
                    return 'market-price-overpriced-up';

            }
            return '';
        }


        var getPriceDifferenceText = function (priceRetail, marketPrice, dealTypeId, isMarketText) {

            var priceDifferenceText = '';
            var diff = priceRetail - marketPrice;

            if (diff > 0) {
                priceDifferenceText = (isMarketText) ? ' Above Average' : ' MORE';
            } else if (diff < 0) {
                priceDifferenceText = (isMarketText) ? ' Below Average' : ' LESS';
            }

            if (diff < 0) {
                diff = diff * -1;
            }

            return $filter('currency')(diff, $locale.NUMBER_FORMATS.CURRENCY_SYM, 0) + priceDifferenceText;
        }

        var getPriceDifferenceStyle = function (priceRetail, marketPrice) {
            var priceDifferenceText = '';
            var diff = priceRetail - marketPrice;

            if (diff > 0) {
                return 'market-price-overpriced';
            } else if (diff < 0) {
                return 'market-price-great';
            }
            return 'market-price-fair';

        }

        var formatDate = function (recentPriceDate) {
            var d = new Date(recentPriceDate);
            var month = d.getMonth() + 1;
            var day = d.getDate();
            var year = d.getFullYear();
            return month + '/' + day + '/' + year;
        }

        var getRecentPriceData = function (priceRetail, recentPrice, recentPriceDate) {
            if (!priceRetail || !recentPrice || !recentPriceDate) {
                return {};
            }
            var change = 'drop';
            var priceDiff = priceRetail - recentPrice;
            if (recentPrice > priceRetail) {
                change = 'increase';
                priceDiff = recentPrice - priceRetail;
            }

            var priceDate = formatDate(recentPriceDate);
            return {
                PriceChange: change,
                PriceDifference: $filter('currency')(priceDiff, $locale.NUMBER_FORMATS.CURRENCY_SYM, 0),
                PriceChangeDate: priceDate
            }

        }

        var getDaysAtDealer = function (inStockDate) {
            var stockDate = new Date(inStockDate);
            var currentDate = new Date();
            var oneDay = 1000 * 60 * 60 * 24;
            var difference = currentDate.getTime() - stockDate.getTime();
            var numberOfDays = Math.round(difference / oneDay);
            return numberOfDays;
        }

        var getPriceDifferenceReasonText = function (priceRetail, marketPrice, dealTypeId) {

            var diff = priceRetail - marketPrice;
            switch (dealTypeId) {
                case 1:
                    if (diff > 0) {
                        return "Lower Miles";
                    }
                case 2:
                    return '';
                case 3:
                    if (diff < 0) {
                        return "Higher Miles";
                    }
            }

        }

        var getPriceDifferenceReasonArrow = function (priceRetail, marketPrice, dealTypeId) {
            if (dealTypeId == 2) {
                return '';
            }
            var diff = priceRetail - marketPrice;

            if (dealTypeId == 1 && diff > 0) {
                return "market-price-fair-down";
            }

            else if (dealTypeId == 3 && diff < 0) {
                return "market-price-fair-up";
            }
            return '';

        }


        var getPricingData = function (priceRetail, marketPrice, dealTypeId) {
            if (!priceRetail || !marketPrice || !dealTypeId) {
                return null;
            }
            var dealTypeText = getDealTypeText(dealTypeId);
            var priceLabelStyle = getPriceLabel(dealTypeId);
            var subLabelStyle = getPriceSubLabel(dealTypeId);
            var priceStyle = getPriceStyle(dealTypeId);
            var priceDifference = getPriceDifference(priceRetail, marketPrice);
            var priceDifferencePercentage = getPriceDifferencePercentage(priceDifference, marketPrice);
            var priceDifferenceStyle = getPriceDifferenceStyle(priceRetail, marketPrice);
            var priceDifferenceText = getPriceDifferenceText(priceRetail, marketPrice, dealTypeId, true);
            var priceDifferenceSimple = getPriceDifferenceText(priceRetail, marketPrice, dealTypeId, false);
            var priceDifferenceReasonArrow = getPriceDifferenceReasonArrow(priceRetail, marketPrice, dealTypeId);
            var priceDifferenceReasonText = getPriceDifferenceReasonText(priceRetail, marketPrice, dealTypeId);
            var fairPriceArrow = getFairPriceArrow(priceDifference, dealTypeId);
            var priceArrow = getPriceArrow(priceDifference, dealTypeId);
            var priceDifferenceAbsolute = Math.abs(priceDifference);

            return {
                DealTypeText: dealTypeText,
                PriceLabelStyle: priceLabelStyle,
                SubLabelStyle: subLabelStyle,
                PriceStyle: priceStyle,
                PriceDifference: priceDifference,
                PriceDifferencePercentage: priceDifferencePercentage,
                PriceDifferenceStyle: priceDifferenceStyle,
                PriceDifferenceText: priceDifferenceText,
                PriceDifferenceSimple: priceDifferenceSimple,
                FairPriceArrow: fairPriceArrow,
                PriceArrow: priceArrow,
                PriceDifferenceAbsolute: priceDifferenceAbsolute,
                PriceDifferenceReasonText: priceDifferenceReasonText,
                PriceDifferenceReasonArrow: priceDifferenceReasonArrow
            }
        }

        return {
            GetDaysAtDealer: getDaysAtDealer,
            GetRecentPriceData: getRecentPriceData,
            GetPricingData: getPricingData,
            GetPriceLabel: getPriceLabel
        };
    }]);
})(angular.module('AutoSMART.Web.Common'));
angular.module('AutoSMART.Web.Search')
    .directive('fallbacksrc', function () {

        var fallbackSrc = {
            link: function postLink(scope, iElement, iAttrs) {
                
                iElement.bind('error', function () {
                    var itemid = iElement[0].getAttribute('data-itemid');
                    if ((itemid == null) || (iAttrs.fallbacksrc === "")) {
                        angular.element(this).attr("src", iAttrs.nophotosrc);
                        return;
                    }
                    $("#stockphoto-" + itemid).hide();

                    angular.element(this).attr("src", iAttrs.fallbacksrc);

                    iElement[0].parentNode.parentNode.removeAttribute('title');

                    iElement.bind('error', function () {
                        angular.element(this).attr("src", iAttrs.nophotosrc);
                    });
                });
            }
        }
        return fallbackSrc;
    }); 
angular.module('AutoSMART.Web.Search')
    .directive('checkfallbackimage', function () {
        return {
            restrict: 'A',
            scope: {
                primaryimage: '@',
                secondaryimage: '@',
                nophotosrc: '@'
            }, 
            link: function (scope, element) {

                var getImageStyle = function (bgImage) {
                    var style = 'background-image: url("' + bgImage + '");';
                    style += 'background-repeat: no-repeat; background-position: 50% 50%; background-size: contain;';
                    style += 'height: 555px; width: auto;display:block;';
                    return style;

                }

                var img = new Image();
                img.src = scope.primaryimage;
                img.onload = function () {
                    $(element).attr('style', getImageStyle(scope.primaryimage));
                    $(".stock-photo-label").show();
                }
                img.onerror = function() {
                    img.src = scope.secondaryimage;
                    img.onload = function() {
                        $(element).attr('style', getImageStyle(scope.secondaryimage));
                        $(".stock-photo-label").show();
                    }
                    img.onerror = function () {
                        img.src = scope.nophotosrc;
                        img.onload = function() {
                            $(element).attr('style', getImageStyle(scope.nophotosrc));
                            return;
                        }
                    }
                }
            }
        }
    });
angular.module('AutoSMART.Web.Search')
    .directive('searchresultfallbackimage', function () {
        return {           
            link: function (scope, element, attrs) {
                element.bind('error', function () {
                    if (attrs.secondarysrc
                        && (attrs.src != attrs.secondarysrc)) {
                        attrs.$set('src', attrs.nophotosrc);
                    } else if (attrs.src != attrs.nophotosrc) {
                        attrs.$set('src', attrs.nophotosrc);
                    }
                });
            }
        }
    });
/**
 * @ngDoc directive
 * @name ng.directive:paging
 *
 * @description
 * A directive to aid in paging large datasets
 * while requiring a small amount of page
 * information.
 *
 * @element EA
 *
 */
angular.module('bw.paging', []).directive('paging', function () {


    /**
     * The regex expression to use for any replace methods
     * Feel free to tweak / fork values for your application
     */
    var regex = /\{page\}/g;


    /**
     * The angular return value required for the directive
     * Feel free to tweak / fork values for your application
     */
    return {

        // Restrict to elements and attributes
        restrict: 'EA',

        // Assign the angular link function
        link: fieldLink,

        // Assign the angular directive template HTML
        template: fieldTemplate,

        // Assign the angular scope attribute formatting
        scope: {
            page: '@',
            pageSize: '=',
            total: '=',
            disabled: '@',
            dots: '@',
            ulClass: '@',
            activeClass: '@',
            disabledClass: '@',
            adjacent: '@',
            pagingAction: '&',
            pgHref: '@',
            textFirst: '@',
            textLast: '@',
            textNext: '@',
            textPrev: '@',
            textFirstClass: '@',
            textLastClass: '@',
            textNextClass: '@',
            textPrevClass: '@',
            textTitlePage: '@',
            textTitleFirst: '@',
            textTitleLast: '@',
            textTitleNext: '@',
            textTitlePrev: '@'
        }

    };


    /**
     * Link the directive to enable our scope watch values
     *
     * @param {object} scope - Angular link scope
     * @param {object} el - Angular link element
     * @param {object} attrs - Angular link attribute
     */
    function fieldLink(scope, el, attrs) {

        // Hook in our watched items
        scope.$watchCollection('[page,pageSize,total,disabled,pgHref]', function () {
            build(scope, attrs);
        });
    }


    /**
     * Create our template html 
     * We use a function to figure out how to handle href correctly
     * 
     * @param {object} el - Angular link element
     * @param {object} attrs - Angular link attribute
     */
    function fieldTemplate(el, attrs) {
        return '<ul data-ng-hide="Hide" data-ng-class="ulClass"> ' +
            '<li ' +
            'title="{{Item.title}}" ' +
            'data-ng-class="Item.liClass" ' +
            'data-ng-repeat="Item in List"> ' +
            '<a ' +
            (attrs.pgHref ? 'data-ng-href="{{Item.pgHref}}" ' : 'href ') +
            'data-ng-class="Item.aClass" ' +
            'data-ng-click="Item.action()" ' +
            'data-ng-bind="Item.value" tabindex="801"> ' +
            '</a> ' +
            '</li>' +
            '</ul>'
    }


    /**
     * Assign default scope values from settings
     * Feel free to tweak / fork these for your application
     *
     * @param {Object} scope - The local directive scope object
     * @param {Object} attrs - The local directive attribute object
     */
    function setScopeValues(scope, attrs) {

        scope.List = [];
        scope.Hide = false;

        scope.page = parseInt(scope.page) || 1;
        scope.total = parseInt(scope.total) || 0;
        scope.adjacent = parseInt(scope.adjacent) || 2;

        scope.pgHref = scope.pgHref || '';
        scope.dots = scope.dots || '...';

        scope.ulClass = scope.ulClass || 'pagination';
        scope.activeClass = scope.activeClass || 'active';
        scope.disabledClass = scope.disabledClass || 'disabled';

        scope.textFirst = scope.textFirst || '<<';
        scope.textLast = scope.textLast || '>>';
        scope.textNext = scope.textNext || '>';
        scope.textPrev = scope.textPrev || '<';

        scope.textFirstClass = scope.textFirstClass || '';
        scope.textLastClass = scope.textLastClass || '';
        scope.textNextClass = scope.textNextClass || '';
        scope.textPrevClass = scope.textPrevClass || '';

        scope.textTitlePage = scope.textTitlePage || 'Page {page}';
        scope.textTitleFirst = scope.textTitleFirst || 'First Page';
        scope.textTitleLast = scope.textTitleLast || 'Last Page';
        scope.textTitleNext = scope.textTitleNext || 'Next Page';
        scope.textTitlePrev = scope.textTitlePrev || 'Previous Page';

        scope.hideIfEmpty = evalBoolAttribute(scope, attrs.hideIfEmpty);
        scope.showPrevNext = evalBoolAttribute(scope, attrs.showPrevNext);
        scope.showFirstLast = evalBoolAttribute(scope, attrs.showFirstLast);
        scope.scrollTop = evalBoolAttribute(scope, attrs.scrollTop);
        scope.isDisabled = evalBoolAttribute(scope, attrs.disabled);
    }


    /**
     * A helper to perform our boolean eval on attributes
     * This allows flexibility in the attribute for strings and variables in scope
     * 
     * @param {Object} scope - The local directive scope object
     * @param {Object} value - The attribute value of interest
     */
    function evalBoolAttribute(scope, value) {
        return angular.isDefined(value)
            ? !!scope.$parent.$eval(value)
            : false;
    }


    /**
     * Validate and clean up any scope values
     * This happens after we have set the scope values
     *
     * @param {Object} scope - The local directive scope object
     * @param {int} pageCount - The last page number or total page count
     */
    function validateScopeValues(scope, pageCount) {

        // Block where the page is larger than the pageCount
        if (scope.page > pageCount) {
            scope.page = pageCount;
        }

        // Block where the page is less than 0
        if (scope.page <= 0) {
            scope.page = 1;
        }

        // Block where adjacent value is 0 or below
        if (scope.adjacent <= 0) {
            scope.adjacent = 2;
        }

        // Hide from page if we have 1 or less pages
        // if directed to hide empty
        if (pageCount <= 1) {
            scope.Hide = scope.hideIfEmpty;
        }
    }


    /**
     * Assign the method action to take when a page is clicked
     *
     * @param {Object} scope - The local directive scope object
     * @param {int} page - The current page of interest
     */
    function internalAction(scope, page) {

        // Block clicks we try to load the active page
        if (scope.page == page) {
            return;
        }

        // Block if we are forcing disabled 
        if (scope.isDisabled) {
            return;
        }

        // Update the page in scope
        scope.page = page;

        // Pass our parameters to the paging action
        scope.pagingAction({
            page: scope.page,
            pageSize: scope.pageSize,
            total: scope.total
        });

        // If allowed scroll up to the top of the page
        if (scope.scrollTop) {
            scrollTo(0, 0);
        }
    }


    /**
     * Add the first, previous, next, and last buttons if desired
     * The logic is defined by the mode of interest
     * This method will simply return if the scope.showPrevNext is false
     * This method will simply return if there are no pages to display
     *
     * @param {Object} scope - The local directive scope object
     * @param {int} pageCount - The last page number or total page count
     * @param {string} mode - The mode of interest either prev or last
     */
    function addPrevNext(scope, pageCount, mode) {

        // Ignore if we are not showing
        // or there are no pages to display
        if ((!scope.showPrevNext && !scope.showFirstLast) || pageCount < 1) {
            return;
        }

        // Local variables to help determine logic
        var disabled, alpha, beta;

        // Determine logic based on the mode of interest
        // Calculate the previous / next page and if the click actions are allowed
        if (mode === 'prev') {

            disabled = scope.page - 1 <= 0;
            var prevPage = scope.page - 1 <= 0 ? 1 : scope.page - 1;

            if (scope.showFirstLast) {
                alpha = {
                    value: scope.textFirst,
                    title: scope.textTitleFirst,
                    aClass: scope.textFirstClass,
                    page: 1
                };
            }

            if (scope.showPrevNext) {
                beta = {
                    value: scope.textPrev,
                    title: scope.textTitlePrev,
                    aClass: scope.textPrevClass,
                    page: prevPage
                };
            }

        } else {

            disabled = scope.page + 1 > pageCount;
            var nextPage = scope.page + 1 >= pageCount ? pageCount : scope.page + 1;

            if (scope.showPrevNext) {
                alpha = {
                    value: scope.textNext,
                    title: scope.textTitleNext,
                    aClass: scope.textNextClass,
                    page: nextPage
                };
            }

            if (scope.showFirstLast) {
                beta = {
                    value: scope.textLast,
                    title: scope.textTitleLast,
                    aClass: scope.textLastClass,
                    page: pageCount
                };
            }

        }

        // Create the Add Item Function
        var buildItem = function (item, disabled) {
            return {
                title: item.title,
                aClass: item.aClass,
                value: item.aClass ? '' : item.value,
                liClass: disabled ? scope.disabledClass : '',
                pgHref: disabled ? '' : scope.pgHref.replace(regex, item.page),
                action: function () {
                    if (!disabled) {
                        internalAction(scope, item.page);
                    }
                }
            };
        };

        // Force disabled if specified
        if (scope.isDisabled) {
            disabled = true;
        }

        // Add alpha items
        if (alpha) {
            var alphaItem = buildItem(alpha, disabled);
            scope.List.push(alphaItem);
        }

        // Add beta items
        if (beta) {
            var betaItem = buildItem(beta, disabled);
            scope.List.push(betaItem);
        }
    }


    /**
     * Adds a range of numbers to our list
     * The range is dependent on the start and finish parameters
     *
     * @param {int} start - The start of the range to add to the paging list
     * @param {int} finish - The end of the range to add to the paging list
     * @param {Object} scope - The local directive scope object
     */
    function addRange(start, finish, scope) {

        // Add our items where i is the page number
        var i = 0;
        for (i = start; i <= (finish); i++) {

            var pgHref = scope.pgHref.replace(regex, i);
            var liClass = scope.page == i ? scope.activeClass : '';

            // Handle items that are affected by disabled
            if (scope.isDisabled) {
                pgHref = '';
                liClass = scope.disabledClass;
            }


            scope.List.push({
                value: i,
                title: scope.textTitlePage.replace(regex, i),
                liClass: liClass,
                pgHref: pgHref,
                action: function () {
                    internalAction(scope, this.value);
                }
            });
        }
    }


    /**
     * Add Dots ie: 1 2 [...] 10 11 12 [...] 56 57
     * This is my favorite function not going to lie
     *
     * @param {Object} scope - The local directive scope object
     */
    function addDots(scope) {
        scope.List.push({
            value: scope.dots,
            liClass: scope.disabledClass
        });
    }


    /**
     * Add the first or beginning items in our paging list
     * We leverage the 'next' parameter to determine if the dots are required
     *
     * @param {Object} scope - The local directive scope object
     * @param {int} next - the next page number in the paging sequence
     */
    function addFirst(scope, next) {

        addRange(1, 2, scope);

        // We ignore dots if the next value is 3
        // ie: 1 2 [...] 3 4 5 becomes just 1 2 3 4 5
        if (next != 3) {
            addDots(scope);
        }
    }


    /**
     * Add the last or end items in our paging list
     * We leverage the 'prev' parameter to determine if the dots are required
     *
     * @param {int} pageCount - The last page number or total page count
     * @param {Object} scope - The local directive scope object
     * @param {int} prev - the previous page number in the paging sequence
     */
    // Add Last Pages
    function addLast(pageCount, scope, prev) {

        // We ignore dots if the previous value is one less that our start range
        // ie: 1 2 3 4 [...] 5 6  becomes just 1 2 3 4 5 6
        if (prev != pageCount - 2) {
            addDots(scope);
        }

        addRange(pageCount - 1, pageCount, scope);
    }



    /**
     * The main build function used to determine the paging logic
     * Feel free to tweak / fork values for your application
     *
     * @param {Object} scope - The local directive scope object
     * @param {Object} attrs - The local directive attribute object
     */
    function build(scope, attrs) {

        // Block divide by 0 and empty page size
        if (!scope.pageSize || scope.pageSize <= 0) {
            scope.pageSize = 1;
        }

        // Determine the last page or total page count
        var pageCount = Math.ceil(scope.total / scope.pageSize);

        // Set the default scope values where needed
        setScopeValues(scope, attrs);

        // Validate the scope values to protect against strange states
        validateScopeValues(scope, pageCount);

        // Create the beginning and end page values
        var start, finish;

        // Calculate the full adjacency value
        var fullAdjacentSize = (scope.adjacent * 2) + 2;


        // Add the Next and Previous buttons to our list
        addPrevNext(scope, pageCount, 'prev');

        // If the page count is less than the full adjacnet size
        // Then we simply display all the pages, Otherwise we calculate the proper paging display
        if (pageCount <= (fullAdjacentSize + 2)) {

            start = 1;
            addRange(start, pageCount, scope);

        } else {

            // Determine if we are showing the beginning of the paging list
            // We know it is the beginning if the page - adjacent is <= 2
            if (scope.page - scope.adjacent <= 2) {

                start = 1;
                finish = 1 + fullAdjacentSize;

                addRange(start, finish, scope);
                addLast(pageCount, scope, finish);
            }

            // Determine if we are showing the middle of the paging list
            // We know we are either in the middle or at the end since the beginning is ruled out above
            // So we simply check if we are not at the end
            // Again 2 is hard coded as we always display two pages after the dots
            else if (scope.page < pageCount - (scope.adjacent + 2)) {

                start = scope.page - scope.adjacent;
                finish = scope.page + scope.adjacent;

                addFirst(scope, start);
                addRange(start, finish, scope);
                addLast(pageCount, scope, finish);
            }

            // If nothing else we conclude we are at the end of the paging list
            // We know this since we have already ruled out the beginning and middle above
            else {

                start = pageCount - fullAdjacentSize;
                finish = pageCount;

                addFirst(scope, start);
                addRange(start, finish, scope);
            }
        }

        // Add the next and last buttons to our paging list
        addPrevNext(scope, pageCount, 'next');
    }

});



(function (module) {
    module.service('VehicleFavoriteService', ['$http', '$q', '$cookies','NotificationService', function ($http, $q, $cookies, NotificationService) {

        var cookieName = 'favoriteVehicles';

        var data = {
            Favorites: []
        };

        function loadData() {
            var deferred = $q.defer();
            var cookiefavorites = $cookies.get(cookieName);
            if (data.Favorites.length < 1) {
                data.Favorites = (cookiefavorites == null) ? [] : JSON.parse(cookiefavorites);
                var length = data.Favorites.length;

                for (var i = 0, len = data.Favorites.length; i < len; i++) {
                    if (length > 1 && i < 3) {
                        data.Favorites[i].IsSelected = true;
                        continue;
                    }
                    data.Favorites[i].IsSelected = false;
                }
            }  
            deferred.resolve();
        };

        var init = function () {
            return $q.all([

                 loadData(),
            ]);
        };

        var setFavoriteCompare = function (itemID) {
            var index = getIndexByItemid(itemID);

            data.Favorites[index].IsSelected = !data.Favorites[index].IsSelected;
            NotificationService.publish("updatedFavorites", data.Favorites);
        }

        var saveFavoriteVehicle = function (favorite) {

            var index = getIndexByItemid(favorite.ItemID);
            if (index > -1) {
                data.Favorites.splice(index, 1);
                $cookies.put(cookieName, JSON.stringify(data.Favorites));
                $('.vehicleSaveCount').text( data.Favorites.length);
                return;
            }

            if (data.Favorites.length === 5) {
                data.Favorites.splice(0, 1);
            }

            favorite.IsSelected = (data.Favorites.length < 3) ? true : false;
            
            data.Favorites.push(favorite);
            $cookies.put(cookieName, JSON.stringify(data.Favorites));
            $('.vehicleSaveCount').text(data.Favorites.length);
            NotificationService.publish("updatedFavorites", data.Favorites);
        }

        var getIndexByItemid = function (vehicleKey) {
            var index = -1;
            for (var i = 0, len = data.Favorites.length; i < len; i++) {
                if (data.Favorites[i].ItemID === vehicleKey) {
                    {
                        isFound = true;
                        index = i;
                        break;
                    }
                }
            }
            return index;
        }

        var getVehicleByItemid = function (vehicleKey) {
            var index = getIndexByItemid(vehicleKey);
            return (index > -1);
        }

        return {
            Init: init,
            Data: data,
            SaveFavoriteVehicle: saveFavoriteVehicle,
            GetVehicleByItemid: getVehicleByItemid,
            SetFavoriteCompare: setFavoriteCompare
        }
    }]);

})(angular.module('AutoSMART.Web.Common'));
'use strict';

(function (module) {
    module.service('DistanceCalculationService', [ function () {
        var  MEAN_RADIUS_MILES = 3969.0;

        var degreesToRadians = function (degrees) {
            return degrees * Math.PI / 180.0;
        }

        var calculateDistance = function(lat1, long1, lat2, long2) {
            var deltaLong = degreesToRadians(long2 - long1);
            var deltaLat = degreesToRadians(lat2 - lat1);
            var lat1Radians = degreesToRadians(lat1);
            var lat2Radians = degreesToRadians(lat2);

            var a = (Math.sin(deltaLat / 2) * Math.sin(deltaLat / 2)) +
            (Math.sin(deltaLong / 2) * Math.sin(deltaLong / 2) *
                Math.cos(lat1Radians) * Math.cos(lat2Radians));
            var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
            return Math.round(Math.abs(c * MEAN_RADIUS_MILES));
        }

        return {
            CalculateDistance: calculateDistance
        }
    }]);

})(angular.module('AutoSMART.Web.Common'));
'use strict';

(function (module) {
    module.service('InventoryImageSizeService', ['$http', '$q', function ($http, $q) {

        var mainUrl = window.location.protocol + '//' + window.location.host + '/';

        var images = {
            Compare: 4,
            SRPVehicle: 8,
            SRPSpotlightImage: 9,
            VDPLarge: 10,
        };



        var init = function () {
        };

        return {
            Init: init,
            images: images
        }
    }]);

})(angular.module('AutoSMART.Web.Common'));
'use strict';

(function (module) {
    module.service('HidePriceDifferenceService', [
        function () {

            // Returns boolean if dealer is from TX
            var isDealerFromTexas = function (location, locationAlt) {

                // Passing into 2 separate possible dealer state location properties
                // One with city and state and one with only state depending if we are in SRP or VPD
                if ((location && !locationAlt) || (!location && locationAlt)) {

                    // Force state abbreviation to uppercase just in case
                    var state = (location) ? location.toUpperCase() : locationAlt.toUpperCase();

                    // if parameter contains city and state. Isolate state abbreviation
                    if (state.length > 2) {
                        state = state.slice(-2);
                    }

                    if (state === "TX") {
                        return true;
                    }
                    return false;
                }
                return false;
            };

            return {
                IsDealerFromTexas: isDealerFromTexas,
            }
        }]);

})(angular.module('AutoSMART.Web.Common'));
'use strict';

(function (module) {
    module.service('UserService', ['$http', '$q', 'Config', function ($http, $q) {
        var cookieName = "UserSettings";

        var userContent = {
            FirstName: '',
            LastName: '',
            EmailAddress: '',
            QQTermOfService: false
        };
       
        function saveCookieSetting() {
            var obj = formatCalculatorSettingsForCookie();
            $.cookie(cookieName, JSON.stringify(obj), { path: '/', secure: true });
        };

        function saveUserSettings(firstName, lastName, qqTermsCheck) {
            userContent.FirstName = firstName;
            userContent.LastName = lastName;
            userContent.QQTermOfService = qqTermsCheck;
        };

        return {
            SaveUserSettings: saveUserSettings,
            SaveCookieSetting: saveCookieSetting
        }
    }]);

})(angular.module('AutoSMART.Web.Common'));

(function (module) {

    module.component('paymentcalculator', {
        templateUrl: "/App/Common/Views/paymentCalculator.html",
        bindings: {
            data: "="
        },
        controller: ["$scope", "CalculatorService", "LeadService", "Config", "$location", "$sce", "GoogleAnalyticsService", "AmplitudeService",
            function ($scope, CalculatorService, LeadService, Config, $location, $sce, GoogleAnalyticsService, AmplitudeService) {
                var ctrl = this;
                var pageSource = null;
                ctrl.showButton = true;
                ctrl.$onInit = function () {

                    if ($scope.$parent.$parent.$ctrl.page == "SRP") {
                        pageSource = "Search Result Page";
                    }
                    if ($scope.$parent.$parent.$ctrl.page == "VDP") {
                        pageSource = "Vehicle Details Page";
                    }                 	

                    ctrl.attemptedSubmission = false;
                    ctrl.leadAlreadySubmitted = false;
                    ctrl.creditslider = {
                        value: CalculatorService.CalcContent.CurrentSelectedScore.Score,
                        options: {
                            stepsArray: CalculatorService.DefaultOptions.CreditScoreDisplay,
                            onChange: function () {
                                updateCreditScore();
                            },
                            hideLimitLabels: true,
                            hidePointerLabels:true,
                            showSelectionBar: true,
                            ariaLabel: 'Credit Score'
                        }
                    };
                    
                    ctrl.ShowLeadForm = false;
                    ctrl.ShowCloseModal = false;
                    ctrl.ClientLogo = Config.ClientLogo;
                    ctrl.LegalDisclaimer = ctrl.HtmlTagConvert(Config.LegalEditPaymentModal);
                    ctrl.vehicle = $scope.$parent.$ctrl.vehicle;

                    if (!ctrl.vehicle.hasOwnProperty("MakeModel")) {
                        ctrl.vehicle.MakeModel = ctrl.vehicle.Make + " " + ctrl.vehicle.Model;
                    }
                    ctrl.DealerName = ctrl.vehicle.SellerInfo?ctrl.vehicle.SellerInfo.SellerName:ctrl.vehicle.DealerName;
                    ctrl.vehicle.IsPreferredPlus = ((ctrl.vehicle.SellerInfo) ? ctrl.vehicle.SellerInfo.IsPreferredPlus : (ctrl.vehicle.IsAutoSmartXLive? ctrl.vehicle.IsAutoSmartXLive : false));
                    ctrl.SelectedCreditScore = CalculatorService.CalcContent.CurrentSelectedScore.Score;
                    ctrl.MinRate = CalculatorService.CalcContent.CurrentSelectedScore.MinRate;
                    ctrl.MaxRate = CalculatorService.CalcContent.CurrentSelectedScore.MaxRate;
                    ctrl.LoanTermList = CalculatorService.CalcContent.LoanTerms;
                    ctrl.CreditScoreList = [];

                    ctrl.calcContent = {
                        CashRebate: CalculatorService.CalcContent.CashRebate,
                        TradeInValue: CalculatorService.CalcContent.TradeInValue,
                        AmountOwed: CalculatorService.CalcContent.AmountOwed,
                        DownPayment: CalculatorService.CalcContent.DownPayment,
                        LoanDuration: CalculatorService.CalcContent.LoanDuration
                    }; 
                    
                    AmplitudeService.logEvent('Edit Payment Modal Loaded',
                        {
                            'Source Page': pageSource,
                            'Vehicle Type': ctrl.vehicle.PhysicalCondition,
                            'Make': ctrl.vehicle.MakeModel.substr(0, ctrl.vehicle.MakeModel.indexOf(' ')),
                            'Model': ctrl.vehicle.MakeModel.substr(ctrl.vehicle.MakeModel.indexOf(' ') + 1)
                        });
                    angular.forEach(CalculatorService.CalcContent.CreditScore, function (score) {
                        ctrl.CreditScoreList.push(score.Score);
                    });
                    
                    $scope.LoanTerm = {
                        value: ctrl.calcContent.LoanDuration,
                        options: {
                            showSelectionBar: true,
                            stepsArray: ctrl.LoanTermList,
                            hidePointerLabels: true,
                            hideLimitLabels: true,
                            ariaLabel: 'Loan Term Length',
                            translate: function(value) {
                                return value + ' mo';
                            }
                        }
                    };

                    $scope.CreditScore = {
                        value: ctrl.SelectedCreditScore, 
                        options: {
                            stepsArray: ctrl.CreditScoreList,
                            showSelectionBar: true,
                            hidePointerLabels: true,
                            hideLimitLabels: true,
                            ariaLabel: 'Credit Score'
                        }
                    };
                };

                $scope.ContinueFinancing = function()
                {
                    if ($scope.$parent.$ctrl.page == 'SRP') {
                        pageSource = "Search Result Page";
                        ctrl.ContinueFinanceBtn = GoogleAnalyticsService.ContinueFinanceBtn;
                        ctrl.ContinueFinanceBtn();
                    }
                    if ($scope.$parent.$ctrl.page == 'VDP') {
                        pageSource = "Vehicle Details Page";
                        ctrl.ContinueFinanceVDPBtn = GoogleAnalyticsService.ContinueFinanceVDPBtn;
                        ctrl.ContinueFinanceVDPBtn();
                    }

                    AmplitudeService.logEvent('Payment Edited',
                        {
                            'Source Page': pageSource,
                            'Vehicle Type': ctrl.vehicle.PhysicalCondition,
                            'Make': ctrl.vehicle.MakeModel.substr(0, ctrl.vehicle.MakeModel.indexOf(' ')),
                            'Model': ctrl.vehicle.MakeModel.substr(ctrl.vehicle.MakeModel.indexOf(' ') + 1),
                            'Dealer Price': '$' + ctrl.vehicle.PriceRetail.toString().replace(/(\d)(?=(\d{3})+$)/g, '$1,'),
                            'Rate': ctrl.MinRate + '%',
                            'Term': ctrl.calcContent.LoanDuration + ' Months',
                            'Estimated Credit Score': ctrl.creditslider.value,
                            'Cash Rebate': '$' + ctrl.calcContent.CashRebate.toString().replace(/(\d)(?=(\d{3})+$)/g, '$1,'),
                            'Amount Owed': '$' + ctrl.calcContent.AmountOwed.toString().replace(/(\d)(?=(\d{3})+$)/g, '$1,'),
                            'Trade-In': '$' + ctrl.calcContent.TradeInValue.toString().replace(/(\d)(?=(\d{3})+$)/g, '$1,'),
                            'Down Payment': '$' + ctrl.calcContent.DownPayment.toString().replace(/(\d)(?=(\d{3})+$)/g, '$1,')
                        });

                    ctrl.ShowLeadForm = true;
                };

                $scope.cancel = function (isSettingSave) {
                    if ($scope.$parent.$ctrl.page == 'SRP') {
                        pageSource = "Search Result Page";
                    }
                    if ($scope.$parent.$ctrl.page == 'VDP') {
                        pageSource = "Vehicle Details Page";
                    }
                    AmplitudeService.logEvent('Payment Edited',
                        {
                            'Source Page': pageSource,
                            'Vehicle Type': ctrl.vehicle.PhysicalCondition,
                            'Make': ctrl.vehicle.MakeModel.substr(0, ctrl.vehicle.MakeModel.indexOf(' ')),
                            'Model': ctrl.vehicle.MakeModel.substr(ctrl.vehicle.MakeModel.indexOf(' ') + 1),
                            'Dealer Price': '$' + ctrl.vehicle.PriceRetail.toString().replace(/(\d)(?=(\d{3})+$)/g, '$1,'),
                            'Rate': ctrl.MinRate + '%',
                            'Term': ctrl.calcContent.LoanDuration + ' Months',
                            'Estimated Credit Score': ctrl.creditslider.value,
                            'Cash Rebate': '$' + ctrl.calcContent.CashRebate.toString().replace(/(\d)(?=(\d{3})+$)/g, '$1,'),
                            'Amount Owed': '$' + ctrl.calcContent.AmountOwed.toString().replace(/(\d)(?=(\d{3})+$)/g, '$1,'),
                            'Trade-In': '$' + ctrl.calcContent.TradeInValue.toString().replace(/(\d)(?=(\d{3})+$)/g, '$1,'),
                            'Down Payment': '$' + ctrl.calcContent.DownPayment.toString().replace(/(\d)(?=(\d{3})+$)/g, '$1,')
                        });

                    if (isSettingSave) {
                        $scope.saveCalcSettings();
                    }
                    $scope.$parent.$parent.modalCalcInstance.close();
                };

                function updateCreditScore() {
                    angular.forEach(CalculatorService.CalcContent.CreditScore, function (score) {
                        if (score.Score === ctrl.creditslider.value) {
                            ctrl.MinRate = score.MinRate;
                            ctrl.MaxRate = score.MaxRate;
                        }
                    });
                };

                $scope.SubmitQuote = function () {
                    ctrl.showButton = false;
                    $scope.saveCalcSettings();
                    ctrl.attemptedSubmission = true;
                    if ($scope.paymentCalculator.$invalid || ctrl.leadAlreadySubmitted) {
                        return;
                    }

                    ctrl.leadAlreadySubmitted = true;
                    ctrl.vehicle.APRDuration = CalculatorService.CalcContent.LoanDuration;
                    ctrl.vehicle.APRMinimum = CalculatorService.CalcContent.CurrentSelectedScore.MinRate;
                    ctrl.vehicle.APRMaximum = CalculatorService.CalcContent.CurrentSelectedScore.MaxRate;
                    ctrl.vehicle.APRMinimumPrice = CalculatorService.CalculatePayment(ctrl.vehicle.PriceRetail, CalculatorService.CalcContent.CurrentSelectedScore.MinRate);
                    ctrl.vehicle.APRMaximumPrice = CalculatorService.CalculatePayment(ctrl.vehicle.PriceRetail, CalculatorService.CalcContent.CurrentSelectedScore.MaxRate);
                    ctrl.vehicle.DownPayment = CalculatorService.CalcContent.DownPayment;
                    LeadService.MapLeadContents(ctrl.vehicle);
                    LeadService.SaveLead().then(function (guid) {
                        if (ctrl.vehicle.IsPreferredPlus) {
                            if (guid) {
                                var url = $location.protocol() + '://' + location.host + '/Certificate/membercertificate?membercertificateguid=' + guid + '&returnUrl=' + encodeURIComponent(window.location.href);
                                window.location.href = url;
                            }
                        }
                        else {
                            ctrl.ShowCloseModal = true;
                        }
                    });
                }

                $scope.saveCalcSettings = function () {
                    CalculatorService.CalcContent.CashRebate = isNaN(ctrl.calcContent.CashRebate) ? 0 : ctrl.calcContent.CashRebate;
                    CalculatorService.CalcContent.TradeInValue = isNaN(ctrl.calcContent.TradeInValue) ? 0 : ctrl.calcContent.TradeInValue;
                    CalculatorService.CalcContent.AmountOwed = isNaN(ctrl.calcContent.AmountOwed) ? 0 : ctrl.calcContent.AmountOwed;
                    CalculatorService.CalcContent.DownPayment = isNaN(ctrl.calcContent.DownPayment) ? 0 : ctrl.calcContent.DownPayment;
                    CalculatorService.CalcContent.LoanDuration = isNaN(ctrl.calcContent.LoanDuration) ? 0 : ctrl.calcContent.LoanDuration;

                    angular.forEach(CalculatorService.CalcContent.CreditScore, function (score) {
                        if (score.Score === ctrl.creditslider.value) {

                            CalculatorService.CalcContent.CurrentSelectedScore = score;
                        }
                    });
                    CalculatorService.SaveCookieSetting();
                };

                $scope.hasRequiredValidValues = function () {
                    return ((ctrl.vehicle.UserName && ctrl.vehicle.UserName.length > 0) && (ctrl.vehicle.UserEmailAddress && ctrl.vehicle.UserEmailAddress.length > 0));
                }

                ctrl.HtmlTagConvert = function(convert) {
                    var convertTags = convert.replace("&lt;p&gt;", "<p>");
                    convertTags = convertTags.replace("&lt;/p&gt;", "</p>");
                    return $sce.trustAsHtml(convertTags);
                }
            }]
    });
})(angular.module('AutoSMART.Web.Common'));

(function (module) {
    module.component('editqqselection', {
        templateUrl: '/App/Common/Views/EditQQSelection.html',
        bindings: {
            data: '='
        },
        controller: ['$scope', 'Config', 'CalculatorService', '$sce', 'QuickQuoteService', 'AmplitudeService',
            function ($scope, Config, CalculatorService, $sce, QuickQuoteService, AmplitudeService) {
                var ctrl = this;
                var pageSource = null;

                ctrl.$onInit = function () {
                    ctrl.isQuickQuoteEnabled = Config.IsQuickQuoteEnabled;
                    ctrl.vehicle = $scope.$parent.$ctrl.vehicle;


                    if (!ctrl.vehicle.hasOwnProperty('MakeModel'))
                        ctrl.vehicle.MakeModel = ctrl.vehicle.Make + ' ' + ctrl.vehicle.Model;

                    ctrl.DealerName = ctrl.vehicle.SellerInfo
                        ? ctrl.vehicle.SellerInfo.SellerName : ctrl.vehicle.DealerName;
                    ctrl.LegalDisclaimer = ctrl.HtmlTagConvert(Config.LegalEditPaymentModal);

                    if (ctrl.vehicle.PhysicalCondition.toLowerCase() === 'new') {
                        ctrl.rateSelected = QuickQuoteService.GetNewQQSelection();
                    } else {
                        ctrl.rateSelected = QuickQuoteService.GetQQSelection();
                    }

                    ctrl.pqFormValues = QuickQuoteService.GetPreQualFormData();
                    ctrl.QQResults = QuickQuoteService.GetRates(ctrl.vehicle.PriceRetail - ctrl.pqFormValues.DownPayment);
                    ctrl.selectedRateType = ctrl.QQResults[ctrl.vehicle.PhysicalCondition.toLowerCase()];
                    ctrl.validatePositivePayment();
                };

                ctrl.updatePayments = function () {
                    ctrl.QQResults = QuickQuoteService.GetRates(ctrl.vehicle.PriceRetail - ctrl.pqFormValues.DownPayment);
                    ctrl.selectedRateType = ctrl.QQResults[ctrl.vehicle.PhysicalCondition.toLowerCase()];
                    ctrl.validatePositivePayment();
                }

                ctrl.validatePositivePayment= function() {
                    var i;
                    for (i = 0; i < ctrl.selectedRateType.length; i++) {
                        if (ctrl.selectedRateType[i].monthlyPayment < 0) { ctrl.selectedRateType[i].monthlyPayment = 0;}
                    }
                }

                ctrl.selectRate = function (selectedItem) {
                    if (!ctrl.rateSelected || !ctrl.isTheSameRate(selectedItem.approvedRate, selectedItem.approvedTerm, selectedItem.loanProduct, ctrl.rateSelected)) {
                        ctrl.rateSelected = ctrl.selectedRateType.filter(function (item) {
                            return ctrl.isTheSameRate(selectedItem.approvedRate, selectedItem.approvedTerm, selectedItem.loanProduct, item);
                        })[0];
                    }
                };

                ctrl.isTheSameRate = function (approvedRate, approvedTerm, loanProduct, rateObject) {
                    return rateObject
                        && rateObject.approvedRate === approvedRate
                        && rateObject.approvedTerm === approvedTerm
                        && rateObject.loanProduct === loanProduct;
                };

                ctrl.HtmlTagConvert = function (convert) {
                    var convertTags = convert.replace('&lt;p&gt;', '<p>');
                    convertTags = convertTags.replace('&lt;/p&gt;', '</p>');
                    return $sce.trustAsHtml(convertTags);
                };

                
                $scope.cancel = function (saveCalc) {
                    if (saveCalc) {
                        $scope.saveCalcSettings();
                    }
                    if ($scope.$parent.$parent.modalRebateInstance) {
                        $scope.$parent.$parent.modalRebateInstance.close();
                    }
                    else if ($scope.$parent.$parent.modalCalcInstance) {
                        $scope.$parent.$parent.modalCalcInstance.close();
                    }
                    else if ($scope.$parent.$parent.modalLeadInstance) {
                        $scope.$parent.$parent.modalLeadInstance.close();
                    }
                    if ($scope.$parent.$parent.$ctrl.page == "SRP") {
                        pageSource = "Search Result Page";
                    }
                    if ($scope.$parent.$parent.$ctrl.page == "VDP") {
                        pageSource = "Vehicle Details Page";
                    }
                    
                    AmplitudeService.logEvent('Financing Updated',
                        {
                            'Source Page': pageSource,
                            'Vehicle Type': ctrl.vehicle.PhysicalCondition,
                            'Make': ctrl.vehicle.MakeModel.substr(0,ctrl.vehicle.MakeModel.indexOf(' ')),
                            'Model': ctrl.vehicle.MakeModel.substr(ctrl.vehicle.MakeModel.indexOf(' ')+1),
                            'Rate': ctrl.rateSelected.approvedRate + '%',
                            'Term': ctrl.rateSelected.approvedTerm + ' Months',
                            'Monthly Payment ': '$' + ctrl.rateSelected.monthlyPayment.toString().replace(/(\d)(?=(\d{3})+$)/g, '$1,') + '/mo'
                        });
                };                

                $scope.saveCalcSettings = function () {
                    QuickQuoteService.SavePreQualFormData(ctrl.pqFormValues);
                    CalculatorService.UpdateDownPayment(ctrl.pqFormValues.DownPayment);
                    CalculatorService.UpdateQQSelection(ctrl.rateSelected);
                    CalculatorService.SaveCookieSetting();
                };
            }
        ]
    });
})(angular.module('AutoSMART.Web.Common'));

"use strict";

(function (module) {

    module.component('favoritescomponent',
    {
        templateUrl: UrlContent("/App/Common/Views/VehicleFavoriteComponent.html"),
        controller: ["$scope", "VehicleFavoriteService", "InventoryImageSizeService", "NotificationService", '$window', "AmplitudeService", function ($scope, VehicleFavoriteService, InventoryImageSizeService, NotificationService,  $window, AmplitudeService) {
            var $ctrl = this;
            var mainUrl = window.location.protocol + '//' + window.location.host + '/';
            var comparisonPage = 'Compare/Index?itemIds=';
            var vehicleDetailsPage = 'Vehicle/Details/';
            $ctrl.SavedItemCount = 5;
            $ctrl.ariaHidden = true;
            
            $ctrl.ToggleFavorites = function () {
                $ctrl.showFavorites = !$ctrl.showFavorites;
                $ctrl.ariaHidden = !$ctrl.ariaHidden;

                var sourcePage = '';
                var currentUrl = window.location.href.toLowerCase();
                if (currentUrl.includes(window.location.hostname.toLowerCase())) {
                    sourcePage = 'landing page';
                }
                if (currentUrl.includes('search')) {
                    sourcePage = 'search results page';
                }
                if (currentUrl.includes('details')) {
                    sourcePage = 'vehicle details page';
                }
                if (currentUrl.includes('dealer')) {
                    sourcePage = 'dealer page';
                }
                if (currentUrl.includes('dealerclientcode')) {
                    sourcePage = 'dealer dedicated page';
                }
                if (currentUrl.includes('finance')) {
                    sourcePage = 'finance page';
                }
                if (currentUrl.includes('build')) {
                    sourcePage = 'build  page';
                }

                AmplitudeService.logEvent("Vehicle Compare", {
                    'source': sourcePage
                });
            };

            $ctrl.VDPImageSize = InventoryImageSizeService.images.SRPVehicle;

            $ctrl.CompareSavedVehicle = function (itemID) {
                $ctrl.SetFavoriteCompare(itemID);
            };

            $ctrl.CompareVehicleSearch = function() {
                var comparisonString = '';
                for (var i = 0, len = $ctrl.Favorites.length; i < len; i++) {
                    if ($ctrl.Favorites[i].IsSelected) {
                        comparisonString = comparisonString + $ctrl.Favorites[i].ItemID.toString() + '|';
                    } 
                }
                comparisonString = comparisonString.slice(0, -1);
                return (mainUrl + comparisonPage + comparisonString);
            };

            $ctrl.VehicleDetailSearch = function (itemID) {
                return (mainUrl + vehicleDetailsPage + itemID.toString() + '/?itemviewsource=SavedVehicleList');
            };

            var favoriteList = function() {
                VehicleFavoriteService.Init()
                    .then(function() {
                        $ctrl.Favorites = VehicleFavoriteService.Data.Favorites;
                        $ctrl.SavedItemCount = VehicleFavoriteService.Data.Favorites.length;
                        $ctrl.RemoveFromFavorites = VehicleFavoriteService.SaveFavoriteVehicle;
                        $ctrl.VehicleComparison = VehicleFavoriteService.Data.VehicleComparison;
                        $ctrl.vehicleCompareAdd = VehicleFavoriteService.SaveVehicleCompare;
                        $ctrl.SetFavoriteCompare = VehicleFavoriteService.SetFavoriteCompare;
                    });
            };

            $ctrl.Remove = function (vehicle) { 
                $ctrl.RemoveFromFavorites(vehicle);
            };
            $ctrl.$onInit = function () {
                favoriteList();
            }

            $ctrl.stockPhotoMessage = 'This image is a stock photo and is not an exact representation of any vehicle offered for sale. Advertised vehicles of this model may have styling, trim levels, colors and optional equipment that differ from the stock photo.';

            NotificationService.subscribe("updatedFavorites",$scope, favoriteList);
        }]

    });
})(angular.module('AutoSMART.Web.Common'));

"use strict";

(function (module) {

    module.component('favoritesmobilecomponent',
    {
        templateUrl: UrlContent("/App/Common/Views/VehicleFavoriteMobileComponent.html"),
        controller: ["$scope", "$cookies", "VehicleFavoriteService", "InventoryImageSizeService", "ZipCodeService", "DistanceCalculationService", "NotificationService", function ($scope, $cookies, VehicleFavoriteService, InventoryImageSizeService, ZipCodeService, DistanceCalculationService, NotificationService) {
            var cookieName = 'favoriteVehicles';
            var $ctrl = this;
            var mainUrl = window.location.protocol + '//' + window.location.host + '/';
            var comparisonPage = 'Compare/Index?itemIds=';
            var vehicleDetailsPage = 'Vehicle/Details/';
            $ctrl.SavedItemCount = 5;

            $ctrl.ToggleFavorites = function () {
                $ctrl.showFavorites = !$ctrl.showFavorites;
            };

            $ctrl.VDPImageSize = InventoryImageSizeService.images.SRPVehicle;

            $ctrl.CompareSavedVehicle = function (itemID) {
                $ctrl.SetFavoriteCompare(itemID);
            };

            $ctrl.CompareVehicleSearch = function () {
                var comparisonString = '';
                for (var i = 0, len = $ctrl.Favorites.length; i < len; i++) {
                    if ($ctrl.Favorites[i].IsSelected) {
                        comparisonString = comparisonString + $ctrl.Favorites[i].ItemID.toString() + '|';
                    }
                }
                comparisonString = comparisonString.slice(0, -1);
                return (mainUrl + comparisonPage + comparisonString);
            };

            $ctrl.VehicleDetailSearch = function (itemID) {
                return (mainUrl + vehicleDetailsPage + itemID.toString() + '/?itemviewsource=SavedVehicleList');
            };

            var favoriteList = function () {
                VehicleFavoriteService.Init()
                    .then(function () {
                        $ctrl.Favorites = VehicleFavoriteService.Data.Favorites;
                        $ctrl.SavedItemCount = VehicleFavoriteService.Data.Favorites.length;
                        $ctrl.RemoveFromFavorites = VehicleFavoriteService.SaveFavoriteVehicle;
                        $ctrl.VehicleComparison = VehicleFavoriteService.Data.VehicleComparison;
                        $ctrl.vehicleCompareAdd = VehicleFavoriteService.SaveVehicleCompare;
                        $ctrl.SetFavoriteCompare = VehicleFavoriteService.SetFavoriteCompare;
                    });
            };

            var updateFavoriteDistance = function () {
                ZipCodeService.Init()
                    .then(function() {
                        var userLatitude = ZipCodeService.Location.Latitude;
                        var userLongitude = ZipCodeService.Location.Longitude;
                        
                        for (var i = 0, len = $ctrl.Favorites.length; i < len; i++) {
                            var distance = DistanceCalculationService.CalculateDistance(userLatitude, userLongitude, $ctrl.Favorites[i].Latitude, $ctrl.Favorites[i].Longitude);
                            $ctrl.Favorites[i].Distance = distance;

                        }
                        $cookies.put(cookieName, JSON.stringify($ctrl.Favorites));
                    });
            }

            $ctrl.Remove = function (vehicle) {
                $ctrl.RemoveFromFavorites(vehicle);
            };
            $ctrl.$onInit = function () {
                favoriteList();
                $scope.setupFavorites();
            }

            NotificationService.subscribe("updatedFavorites", $scope, favoriteList);
            NotificationService.subscribe("saveZipCode", $scope, updateFavoriteDistance);

            $scope.setupFavorites = function () {
                window.setTimeout(function () {
                    $('.favorite-vehicles .toggler').on('click', function () {
                        $scope.toggleFavorites(this);
                    });
                }, 100);
            }

            $scope.toggleFavorites = function (elem) {
                var $elem = $(elem);
                var $items = $elem.parents('.favorite-vehicles').find('.nav');
                var goToHeight = 0;

                if ($items.height() === 0) {
                    var $sliderControl = $items.find('.slider-control');
                    if ($sliderControl.length > 0) {
                        if ($sliderControl.attr('data-toggle-height')) {
                            goToHeight = $sliderControl.data('toggle-height');
                        } else {
                            goToHeight = 85;
                        }
                    } else {
                        if ($items.attr('data-max-height')) {
                            goToHeight = $items.data('max-height');
                        } else {
                            goToHeight = 275;
                        }
                    }
                }

                $items.velocity({
                    height: goToHeight
                }, 500, function () {
                    if ($elem.hasClass('toggler-down')) {
                        $('.compareSavedContainer').show();
                        $('.vehicleSavedContainer').show();
                        $elem.addClass('toggler-up').removeClass('toggler-down');
                    } else {
                        $('.compareSavedContainer').hide();
                        $('.vehicleSavedContainer').hide();
                        $elem.addClass('toggler-down').removeClass('toggler-up');
                    }

                }, 'easeInOutQuad');
            };
        }]

    });
})(angular.module('AutoSMART.Web.Common'));

"use strict";

(function (module) {

    module.component('favoritestogglecomponent',
    {
        bindings: {
            data: '<',
            page: '@'
        },
        templateUrl: UrlContent("/App/Common/Views/VehicleFavoriteToggleComponent.html"),
        controller: ["$scope", "VehicleFavoriteService", "GoogleAnalyticsService", function ($scope, VehicleFavoriteService, GoogleAnalyticsService) {
            var ctrl = this;

            ctrl.$onInit = function () {
            }
            
            $scope.clickedFavorite = function ($event) {
                if (ctrl.page == 'VDP') {
                    if (ctrl.IsFavorite()) {
                        ctrl.RemoveVehicleClick = GoogleAnalyticsService.RemoveVehicleClick;
                        ctrl.RemoveVehicleClick();
                    }
                    else {
                        ctrl.SaveVehicleClick = GoogleAnalyticsService.SaveVehicleClick;
                        ctrl.SaveVehicleClick();
                    }
                }
                $event.stopImmediatePropagation();
                $event.preventDefault();

                var favorite = {
                    ItemID: ctrl.data.ItemID,
                    ImagePath: ctrl.data.Images ? ctrl.data.Images[0].ImagePath : ctrl.data.ImagePath,
                    ItemName: ctrl.data.ItemName,
                    PriceRetail: ctrl.data.PriceRetail,
                    PhysicalCondition: ctrl.data.PhysicalCondition,
                    UsageType: ctrl.data.UsageType,
                    Usage: ctrl.data.Usage,
                    Distance: ctrl.data.Distance,
                    Latitude: ctrl.data.Latitude,
                    Longitude: ctrl.data.Longitude,
                    IsStockPhoto: ctrl.data.IsStockPhoto
                };
                VehicleFavoriteService.SaveFavoriteVehicle(favorite);
                
            };


            ctrl.IsFavorite = function () {
                return VehicleFavoriteService.GetVehicleByItemid(ctrl.data.ItemID);
            }


        }]

    });
})(angular.module('AutoSMART.Web.Common'));

"use strict";


(function (module) {

    module.component('preferredplusinformationcomponent', {
        templateUrl: '/app/Common/views/preferredplusinformationcomponent.html',
        bindings: {
            displaytext : '<',
            displaycreditunionfooter: '<',
        },
        controller: ["Config",

        function (Config) {
            var $ctrl = this;
            
            $ctrl.CreditUnionName = Config.ClientName;
            $ctrl.Logo = Config.ClientLogo;
            $ctrl.PrimaryColor = Config.PrimaryColor;
            $ctrl.SecondaryColor = Config.SecondaryColor.toLowerCase();
            if (!$ctrl.SecondaryColor || $ctrl.SecondaryColor === "#fff" || $ctrl.SecondaryColor === "#ffffff") {
                $ctrl.SecondaryColor = $ctrl.PrimaryColor;
            }
            $ctrl.svgcontent = {
                "TextColor": $ctrl.SecondaryColor,
                "ShieldColor": $ctrl.SecondaryColor
            };
        }]
    });

})(angular.module('AutoSMART.Web.Common'));
"use strict";


(function (module) {

    module.component('vehicledealercontactbutton', {
        templateUrl: '/app/Common/views/VehicleDealerContactButton.html',
        bindings: {
            itemid: '<',
            vehicle: '<',
            page: '@'
            },
        controller: ["$uibModal", "$scope", "Config", "GoogleAnalyticsService",
            
            function ($uibModal, $scope, Config, GoogleAnalyticsService) {
            var $ctrl = this;
            var qqResultCookieName = "qqresult";
            $ctrl.hasMarketPrice = ($ctrl.vehicle.MarketPrice || ($ctrl.vehicle.MarketPriceInfo && $ctrl.vehicle.MarketValue)) && $ctrl.vehicle.PriceRetail !== 0;
            $ctrl.buttonText = "CONTACT";


               $scope.contactDealerClicked = function () {    
                var qqEnabled = Config.IsQuickQuoteEnabled;
                var qqResult = getQQResultCookie();
                var useQqLeadForm = false;
                if (qqEnabled && qqResult !== null) {
                    useQqLeadForm = true;
                }
                var modalInstance;
                   if ($ctrl.page == 'SRP') {
                       if (qqEnabled && qqResult !== null) {
                           $ctrl.ContactDealerBtn = GoogleAnalyticsService.ContactDealerBtn;
                           $ctrl.ContactDealerBtn();
                       }
                       else {
                           $ctrl.ContactDealerNoQQBtn = GoogleAnalyticsService.ContactDealerNoQQBtn;
                           $ctrl.ContactDealerNoQQBtn();

                       }  
                   }

                if (useQqLeadForm) {
                    modalInstance = $uibModal.open({
                        component: "prequalifyvehicleleadcomponent",
                        scope: $scope,
                        windowClass: 'vdplead-modal-content prequalify-vdplead-modal-content'
                    });
                } else {
                    modalInstance = $uibModal.open({
                        component: "Vehicleleadcomponent",
                        scope: $scope,
                        windowClass: 'vdplead-modal-content'
                    });
                }

                $scope.modalRebateInstance = modalInstance;
            };

            function getQQResultCookie() {
                return JSON.parse($.cookie(qqResultCookieName)
                    ? $.cookie(qqResultCookieName)
                    : localStorage.getItem(qqResultCookieName));
            }

            $scope.cancel = function () {
                $scope.modalInstance.close();
            };
            
        }]
    });

})(angular.module('AutoSMART.Web.Search'));

"use strict";


(function (module) {

    module.component('vehicledealercontactprimarybutton', {
        templateUrl: '/app/Common/views/VehicleDealerContactPrimaryButton.html',
        bindings: {
            itemid: '<',
            vehicle: '<',
            page: '@'
            },
        controller: ["$uibModal", "$scope", "Config", "GoogleAnalyticsService",
            
            function ($uibModal, $scope, Config, GoogleAnalyticsService) {
            var $ctrl = this;
            var qqResultCookieName = "qqresult";
            $ctrl.hasMarketPrice = ($ctrl.vehicle.MarketPrice || ($ctrl.vehicle.MarketPriceInfo && $ctrl.vehicle.MarketValue)) && $ctrl.vehicle.PriceRetail !== 0;
            $ctrl.buttonText = "CONTACT";


            $scope.contactDealerClicked = function () {                
                var qqEnabled = Config.IsQuickQuoteEnabled;
                var qqResult = getQQResultCookie();
                var useQqLeadForm = false;
                if (qqEnabled && qqResult !== null) {
                    useQqLeadForm = true;
                }
                var modalInstance;

                if ($ctrl.page == 'VDP') {
                    if (qqEnabled && qqResult !== null) {
                        $ctrl.ContactDealerVDPBtn = GoogleAnalyticsService.ContactDealerVDPBtn;
                        $ctrl.ContactDealerVDPBtn();
                    }
                    else {
                        $ctrl.ContactDealerNoQQVDPBtn = GoogleAnalyticsService.ContactDealerNoQQVDPBtn;
                        $ctrl.ContactDealerNoQQVDPBtn();
                    } 
                }

                if (useQqLeadForm) {
                    modalInstance = $uibModal.open({
                        component: "prequalifyvehicleleadcomponent",
                        scope: $scope,
                        windowClass: 'vdplead-modal-content prequalify-vdplead-modal-content'
                    });
                } else {
                    modalInstance = $uibModal.open({
                        component: "Vehicleleadcomponent",
                        scope: $scope,
                        windowClass: 'vdplead-modal-content'
                    });
                }

                $scope.modalRebateInstance = modalInstance;
            };

            function getQQResultCookie() {
                return JSON.parse($.cookie(qqResultCookieName)
                    ? $.cookie(qqResultCookieName)
                    : localStorage.getItem(qqResultCookieName));
            }

            $scope.cancel = function () {
                $scope.modalInstance.close();
            };
            
        }]
    });

})(angular.module('AutoSMART.Web.Search'));

"use strict";


(function (module) {

  module.component('askaquestionbuttoncomponent', {
    templateUrl: '/app/Common/views/AskAQuestionButton.html',
    bindings: {
      itemid: '<',
      vehicle: '<'
    },
      controller: ["$uibModal", "$scope", "Config", "GoogleAnalyticsService",

          function ($uibModal, $scope, Config, GoogleAnalyticsService) {
        var $ctrl = this;
        var qqResultCookieName = "qqresult";
        $ctrl.hasMarketPrice = ($ctrl.vehicle.MarketPrice || ($ctrl.vehicle.MarketPriceInfo && $ctrl.vehicle.MarketValue)) && $ctrl.vehicle.PriceRetail !== 0;

        $scope.contactDealerClicked = function () {
          $ctrl.AskQuestionBtn = GoogleAnalyticsService.AskQuestionBtn;
          $ctrl.AskQuestionBtn();
          var qqEnabled = Config.IsQuickQuoteEnabled;
          var qqResult = getQQResultCookie();
          var useQqLeadForm = false;
          if (qqEnabled && qqResult !== null) {
            useQqLeadForm = true;
          }
          var modalInstance;

          if (useQqLeadForm) {
            modalInstance = $uibModal.open({
              component: "prequalifyvehicleleadcomponent",
              scope: $scope,
              windowClass: 'vdplead-modal-content prequalify-vdplead-modal-content'
            });
          } else {
            modalInstance = $uibModal.open({
              component: "Vehicleleadcomponent",
              scope: $scope,
              windowClass: 'vdplead-modal-content',
              resolve: {
                  componentname: function () {
                        return 'askquestions';
                    }
                }
            });
          }

          $scope.modalRebateInstance = modalInstance;
        };

        function getQQResultCookie() {
          return JSON.parse($.cookie(qqResultCookieName)
            ? $.cookie(qqResultCookieName)
            : localStorage.getItem(qqResultCookieName));
        }

        $scope.cancel = function () {
          $scope.modalInstance.close();
        };

      }]
  });

})(angular.module('AutoSMART.Web.Search'));

"use strict";


(function (module) {

    module.component('vehiclememberpricebutton', {
        templateUrl: '/app/Common/views/VehicleMemberPriceButton.html',
        bindings: {
            itemid: '<',
            vehicle: '<',
            displayasx: '<',
            page: '@'
            },
        controller: ["$uibModal", "$scope", "Config", "GoogleAnalyticsService",

            function ($uibModal, $scope, Config, GoogleAnalyticsService) {
            var $ctrl = this;
            var qqResultCookieName = "qqresult";
                $scope.memberPricedButtonClicked = function () {                   

                var qqEnabled = Config.IsQuickQuoteEnabled;
                var qqResult = getQQResultCookie();
                var useQqLeadForm = false;
                if (qqEnabled && qqResult !== null) {
                    useQqLeadForm = true;
                }
                var modalInstance;
                    if ($ctrl.page == 'SRP') {
                        if (qqEnabled && qqResult !== null) {
                            $ctrl.ContactDealerBtn = GoogleAnalyticsService.ContactDealerBtn;
                            $ctrl.ContactDealerBtn();
                        }
                        else {
                            $ctrl.ContactDealerNoQQBtn = GoogleAnalyticsService.ContactDealerNoQQBtn;
                            $ctrl.ContactDealerNoQQBtn();

                        }

                    }
                    if ($ctrl.page == 'VDP') {
                        if (qqEnabled && qqResult !== null) {
                            $ctrl.ContactDealerVDPBtn = GoogleAnalyticsService.ContactDealerVDPBtn;
                            $ctrl.ContactDealerVDPBtn();
                        }
                        else {
                            $ctrl.ContactDealerNoQQVDPBtn = GoogleAnalyticsService.ContactDealerNoQQVDPBtn;
                            $ctrl.ContactDealerNoQQVDPBtn();
                        }                        
                    }

                if (useQqLeadForm) {
                    modalInstance = $uibModal.open({
                        component: "prequalifyvehicleleadcomponent",
                        scope: $scope,
                        windowClass: 'vdplead-modal-content prequalify-vdplead-modal-content'
                    });
                } else {
                    modalInstance = $uibModal.open({
                        component: "Vehicleleadcomponent",
                        scope: $scope,
                        windowClass: 'vdplead-modal-content'
                    });
                }

                $scope.modalRebateInstance = modalInstance;
            };

            $scope.cancel = function () {
                $scope.modalInstance.close();
            };

            function getQQResultCookie() {
                return JSON.parse($.cookie(qqResultCookieName)
                    ? $.cookie(qqResultCookieName)
                    : localStorage.getItem(qqResultCookieName));
            }

        }]
    });

})(angular.module('AutoSMART.Web.Search'));
"use strict";

(function (module) {

    module.component('vehicleleadbutton', {
        templateUrl: '/app/Common/views/VehicleLeadButton.html',
        bindings: {
            itemid: '<',
            vehicle: '<',
            buttonsetting: '<',
            dealer: '<',
            buildselection: '<'
        },
        controller: ["$uibModal", "$scope",
            function ($uibModal, $scope) {
                var $ctrl = this;
                
                $scope.leadButtonClicked = function () {
                    var modalInstance = $uibModal.open({
                        component: "vehicleleadcomponent",
                        scope: $scope,
                        windowClass: 'vdplead-modal-content'
                    });

                    $scope.modalLeadInstance = modalInstance;
                };

            $scope.cancel = function () {
                $scope.modalInstance.close();
            };
        }]
    });

})(angular.module('AutoSMART.Web.Common'));
"use strict";

(function (module) {

    module.component('mapcomponent',
    {
        templateUrl: "/App/Common/Views/MapComponent.html?sss",
        bindings: {
            latitude: '=',
            longitude: '=',
            address: '='
        },
        controller: ["$scope" ,

           function ($scope ) {
               var ctrl = this;
               var geocoder = new google.maps.Geocoder();
                   var location;
               var drawMap = function (location) {
                   var mapOptions = {
                       zoom: 13,
                       center: location,
                       mapTypeId: google.maps.MapTypeId.ROADMAP
                   };

                   var map = new google.maps.Map(document.getElementById("map-canvas"), mapOptions);
               
                   var marker = new google.maps.Marker({
                       position: location,
                       draggable: false,
                       animation: google.maps.Animation.DROP,
                       map: map,
                       title: name
                   });
               }
               //event.stopPropagation();

               if (parseFloat(ctrl.latitude) && parseFloat(ctrl.longitude)) {
                   location = new google.maps.LatLng(ctrl.latitude, ctrl.longitude);
                   geocoder.geocode({ 'location': location },
                       function (results, status) {
                           if (status == google.maps.GeocoderStatus.OK) {
                               location = new google.maps
                                   .LatLng(results[0].geometry.location.lat(), results[0].geometry.location.lng());
                               drawMap(location);
                           }
                       });
               } else {
                   geocoder.geocode({ 'address': ctrl.address },
                       function (results, status) {
                           if (status == google.maps.GeocoderStatus.OK) {
                               location = new google.maps
                                   .LatLng(results[0].geometry.location.lat(), results[0].geometry.location.lng());
                               drawMap(location);
                           }
                       });
               }


           }],


    });
})(angular.module('AutoSMART.Web.Common'));

"use strict";


(function (module) {

    module.component('userzipcodedisplay', {

        controller: ["$scope", "$uibModal", "ZipCodeService", "$location",
            function ($scope, $uibModal, ZipCodeService, $location) {

                var $ctrl = this;

                var url = $location.search();

                $ctrl.$onInit = function () {
                    ZipCodeService.Init().then(function () {
                        if (url.ZipCode) {
                            ZipCodeService.SaveZipCode(url.ZipCode);
                        }
                        $ctrl.Location = ZipCodeService.Location;
                    });
                }

                $ctrl.openModal = function () {
                    var modalInstance = $uibModal.open({
                        component: 'zipcodemodal',
                        scope: $scope
                    });
                    $scope.ZipCodeModal = modalInstance;
                }

            }],
        templateUrl: '/app/Common/views/UserZipCode.html?ssss',
    });

})(angular.module('AutoSMART.Web.Common'));
























/*var $ctrl = this;
 
$ctrl.$onInit = function () {
    $ctrl.ZipCode = ZipCodeService.ZipCode;
}


$ctrl.cancel = function () {
    $scope.$parent.$parent.Interstitial.dismiss('cancel');
};


$ctrl.openModal = function () {
    ZipCodeService.ZipCode = 'sfsdfas';
    //$ctrl.ZipCode = 'test';
    console.log('opening')

    var modalInstance = $uibModal.open({
        component: 'zipcodemodal',
        scope: $scope
    });
}

}],
templateUrl: '/app/Common/views/UserZipCode.html?ss',
});

})(angular.module('AutoSMART.Web.Common'));

*/

"use strict";


(function (module) {

    module.component('carfaxmodalcomponent', {
        templateUrl: '/app/Common/views/_CarFaxModal.html',
        controller: ["$scope", "$uibModal", "$sce", "Config", '$http', '$q', 

        function ($scope, $uibModal, $sce, Config, $http, $q) {
            var getCarFaxContent = function (vin) {
                var deferred = $q.defer();
                var link = Config.BaseDirectory + "/Vehicle/CarFaxContent?vin=" + vin;

                $http({
                    method: 'GET',
                    url: link
                })
                .then(function (result) {
                    deferred.resolve(result);
                }, function (errorResult) {
                    deferred.reject(errorResult);
                });
                return deferred.promise;
            }

            var $ctrl = this;
            $ctrl.isLoading = true;
            $ctrl.$onInit = function () {
                $ctrl.CarFaxImageUrl = $scope.$parent.ThirdPartyImgUrl;
                $ctrl.ClientLogo = Config.ClientLogo;
                $ctrl.CreditUnionName = Config.ClientName;
                getCarFaxContent($scope.$parent.VehicleVin).then(function (data) {
                    if (data.data) {
                        data = data.data;
                    }
                    $ctrl.CarFaxLinkUrl = $sce.trustAsResourceUrl(data.carfaxUrl);
                    $ctrl.isLoading = false;
                });
            };
           
            $ctrl.cancel = function () {
                $scope.$parent.$uibModalInstance.dismiss('cancel');
            };
        }]
    });

})(angular.module('AutoSMART.Web.Common'));
"use strict";


(function (module) {

    module.component('zipcodemodal', {

        controller: ["$uibModal", 'ZipCodeService', '$scope', 'NotificationService',

            function ($uibModal, ZipCodeService, $scope, NotificationService) {
                var $ctrl = this;

                $ctrl.ZipCodeValue = "";

                var updateZipCode = function () {
                    $ctrl.Location = ZipCodeService.Location;
                    $ctrl.ZipCodeValue = ZipCodeService.Location.CurrentZipCode;
                    $ctrl.SaveZipCode = function () {
                        ZipCodeService.SaveZipCode($ctrl.ZipCodeValue);
                        $scope.$parent.$parent.ZipCodeModal.dismiss('cancel');
                    }
                };

                $ctrl.$onInit = function () {
                    updateZipCode();
                }
                $ctrl.cancel = function () {
                    $scope.$parent.$parent.ZipCodeModal.dismiss('cancel');
                };


                NotificationService.subscribe("saveZipCode", $scope, updateZipCode);
            }],
        templateUrl: '/app/Common/views/UserZipCodeModal.html?saa',
    });

})(angular.module('AutoSMART.Web.Common'));

"use strict";


(function (module) {

    module.component('zipcodemobilemodal', {

        controller: ['ZipCodeService', '$scope', 'NotificationService',

            function (ZipCodeService, $scope, NotificationService) {
                var $ctrl = this;

                var updateZipCode = function () {
                    ZipCodeService.Init()
                        .then(function () {
                            $ctrl.Location = ZipCodeService.Location;
                            $ctrl.ZipCodeValue = ZipCodeService.Location.CurrentZipCode;
                            $ctrl.SaveZip = ZipCodeService.SaveZipCode;
                        });

                };

                $ctrl.SaveZipCode = function () {
                    $ctrl.SaveZip($ctrl.ZipCodeValue);
                }

                $ctrl.$onInit = function () {
                    updateZipCode();
                }
                NotificationService.subscribe("saveZipCode", $scope, updateZipCode);
            }],
        templateUrl: '/app/Common/views/UserZipCodeMobile.html',
    });

})(angular.module('AutoSMART.Web.Common'));

'use strict';


(function (module) {

    module.component('vehiclecustomizepaymentbutton', {
        templateUrl: '/app/Common/views/VehicleCustomizePaymentButton.html',
        bindings: {
            itemid: '<',
            priceretail: '<',
            vehicle: '<',
            page: '@'
            },
        controller: ['$uibModal', '$scope', '$rootScope', 'QuickQuoteService', 'Config', 'GoogleAnalyticsService',
            function ($uibModal, $scope, $rootScope, QuickQuoteService, Config, GoogleAnalyticsService) {
                var $ctrl = this;               
                $ctrl.$onInit = function () {                    
                    var qqresult = Config.IsQuickQuoteEnabled && QuickQuoteService.GetQQResultCookie();
                    $ctrl.modelName = qqresult ? 'EDIT FINANCING' : 'EDIT PAYMENT';                    
                } 
                $scope.customizedPaymentButtonClicked = function () {
                    if ($ctrl.page == 'SRP') {

                        if ($ctrl.modelName == 'EDIT PAYMENT') {
                            $ctrl.EditPaymentBtn = GoogleAnalyticsService.EditPaymentBtn;
                            $ctrl.EditPaymentBtn();
                        }                           
                        if ($ctrl.modelName == 'EDIT FINANCING') {
                            $ctrl.EditFinancingBtn = GoogleAnalyticsService.EditFinancingBtn;
                            $ctrl.EditFinancingBtn();
                        }
                          
                    }
                    if ($ctrl.page == 'VDP') {

                        if ($ctrl.modelName == 'EDIT PAYMENT') {
                            $ctrl.EditPaymentVDPBtn = GoogleAnalyticsService.EditPaymentVDPBtn;
                            $ctrl.EditPaymentVDPBtn();
                        }
                        if ($ctrl.modelName == 'EDIT FINANCING') {
                            $ctrl.EditFinancingVDPBtn = GoogleAnalyticsService.EditFinancingVDPBtn;
                            $ctrl.EditFinancingVDPBtn();
                        }

                    }

                    var qqResultAvailable = Config.IsQuickQuoteEnabled && QuickQuoteService.GetQQResultCookie();
                    var modalInstance = $uibModal.open(qqResultAvailable? {
                        windowClass: 'edit-qq-selection',
                        component: 'editqqselection',
                        scope: $scope                        
                    } : {
                        windowClass: 'payment-calculator',
                        component: 'paymentcalculator',
                        scope: $scope,
                        controller: function($scope, $uibModalInstance, values) {
                            $scope.credit = JSON.parse(JSON.stringify(values));                            
                            $scope.ok = function () {                                
                                $uibModalInstance.close($scope.credit);
                            };

                            $scope.cancel = function() {
                                $uibModalInstance.dismiss();
                            };
                        },
                        resolve: {
                            values: function() {
                                return $scope.credit;
                            }
                        }
                    });

                    modalInstance.result.then(function (data) {
                        if (data.data) {
                            data = data.data;
                        }
                            $scope.credit = data;
                    });

                    modalInstance.rendered.then(function () {
                        if (!qqResultAvailable)
                            $rootScope.$broadcast('rzSliderForceRender');
                    });

                    $scope.modalCalcInstance = modalInstance;
                };

            }]
    });

})(angular.module('AutoSMART.Web.Search'));
"use strict";

(function (module) {

    module.component('searchviewnearme',
    {
        controller: ["$window", "$scope", 'GoogleAnalyticsService', 'InventorySearchService',
            function ( $window, $scope, GoogleAnalyticsService, InventorySearchService) {
                var ctrl = this;
                ctrl.$onInit = function () {
                    InventorySearchService.GetInventoryCount(ctrl.year, ctrl.make, ctrl.model).then(function (data) {
                        if (data.data) {
                            data = data.data;
                        }
                        ctrl.inventoryCount = data.SearchTotalCount;
                    });
                };
                
                ctrl.viewInventory = function () {
                    ctrl.SearchNearYouGA();
                    var searchUrl = '/Search/?Makes=';
                    $window.location.href = searchUrl.concat(ctrl.make,
                        '&MakeModel=',
                        ctrl.make.concat('|', ctrl.model),
                        '&MinYear=',
                        ctrl.year,
                        '&MaxYear=',
                        ctrl.year,
                        '&Distance=',
                        100,
                        '&Condition=New');
                }

                ctrl.SearchNearYouGA = function () {
                    GoogleAnalyticsService.BuildSearchNearYou();
             }
            }
        ],
        templateUrl: UrlContent("/App/Common/Views/SearchViewNearMeComponent.html"),
        bindings: {
            make: '=',
            model: '=',
            year: '='
        }
    });

})(angular.module('AutoSMART.Web.Common'));
"use strict";

(function (module) {

    module.component('creditunionadscomponent', {

        controller: ["Config", "$scope", "$interval", "$window",
            function (Config, $scope, $interval, $window) {
                var ctrl = this;
                ctrl.ActiveAds = [];

                $scope.ActiveAdOnClick = function (path) {
                    if (path != null && path.trim() != '') {
                        $window.open(path, '_blank');
                    }
                }

                var AdIntervalSlider = function () {
                    var AdObjectLength = ctrl.ActiveAds.length;
                    var AdObjectIndex = 0;
                    if (AdObjectLength > 0) {
                        ctrl.ActiveAds[0].visible = true;
                        $interval(function () {
                            ctrl.ActiveAds.forEach(function (ad) {
                                ad.visible = false; // make every image invisible
                            });
                            ctrl.ActiveAds[AdObjectIndex].visible = true;
                            if (AdObjectIndex === (AdObjectLength - 1)) {
                                AdObjectIndex = 0;
                                return;
                            }
                            AdObjectIndex++;
                        }, 5000);
                    }
                }

                ctrl.$onInit = function () {
                    ctrl.ActiveAds = JSON.parse(Config.CUActiveAds.replace(/&quot;/g, '"'));
                    AdIntervalSlider();
                };

                

            }],
        templateUrl: UrlContent('/App/Common/Views/CreditUnionAds.html')
    });
})(angular.module('AutoSMART.Web.Common'));
"use strict";

(function (module) {

    module.component('buildestimatepaymentcalculatorcomponent',
    {
        controller: ["$scope", "$uibModal", "Config", "$cookies","BuildEstimateCalcService",

            function ($scope, $uibModal, Config, $cookies, BuildEstimateCalcService) {
                var ctrl = this;
                ctrl.MinMonthlyPayment = 0;
                ctrl.MaxMonthlyPayment = 0;

                ctrl.affordabilityCalculator = JSON.parse(Config.AffordabilityCalculator.replace(/&quot;/g, '"'));
                
                ctrl.selectedCredit = ctrl.affordabilityCalculator.CreditScore[1].CreditScore;

                ctrl.UpdateAPR = function (Min, Max, CreditScoreRateId) {
                    ctrl.PaymentCalculatorSettings.minApr = Min;
                    ctrl.PaymentCalculatorSettings.maxApr = Max;
                    ctrl.PaymentCalculatorSettings.SelectedCreditScoreId = CreditScoreRateId;
                };

                ctrl.UpdateMinMaxMonthlyPayment = function() {
                    ctrl.MinMonthlyPayment = ctrl.UpdateMonthlyPayment(
                        ctrl.PaymentCalculatorSettings.minApr,
                        ctrl.PurchasePrice,
                        ctrl.PaymentCalculatorSettings.CashRebate,
                        ctrl.PaymentCalculatorSettings.DownPayment,
                        ctrl.PaymentCalculatorSettings.TradeIn,
                        ctrl.PaymentCalculatorSettings.AmountOwned,
                        ctrl.PaymentCalculatorSettings.LoanDuration);
                    ctrl.MaxMonthlyPayment = ctrl.UpdateMonthlyPayment(
                        ctrl.PaymentCalculatorSettings.maxApr,
                        ctrl.PurchasePrice,
                        ctrl.PaymentCalculatorSettings.CashRebate,
                        ctrl.PaymentCalculatorSettings.DownPayment,
                        ctrl.PaymentCalculatorSettings.TradeIn,
                        ctrl.PaymentCalculatorSettings.AmountOwned,
                        ctrl.PaymentCalculatorSettings.LoanDuration);
                };

                ctrl.closeBuildEstimateCalc = function () {
                    $scope.$parent.$uibModalInstance.close();
                };

                ctrl.saveBuildEstimateCalc = function () {
                    ctrl.PaymentCalculatorSettings.CashRebate = (ctrl.PaymentCalculatorSettings.CashRebate > 0) ? ctrl.PaymentCalculatorSettings.CashRebate : 0;
                    ctrl.PaymentCalculatorSettings.DownPayment = (ctrl.PaymentCalculatorSettings.DownPayment > 0) ? ctrl.PaymentCalculatorSettings.DownPayment : 0;
                    ctrl.PaymentCalculatorSettings.TradeIn = (ctrl.PaymentCalculatorSettings.TradeIn > 0)?ctrl.PaymentCalculatorSettings.TradeIn:0;
                    ctrl.PaymentCalculatorSettings.AmountOwned = (ctrl.PaymentCalculatorSettings.AmountOwned > 0) ? ctrl.PaymentCalculatorSettings.AmountOwned : 0;

                    ctrl.SaveBuildEstimateCalc(ctrl.PaymentCalculatorSettings);
                    ctrl.closeBuildEstimateCalc();
                };

                var paymentCalcSetting = function() {
                    ctrl.PaymentCalculatorSettings = BuildEstimateCalcService.getPaymentCalc();
                    ctrl.PurchasePrice = BuildEstimateCalcService.getPurchasePrice();
                    ctrl.SaveBuildEstimateCalc = BuildEstimateCalcService.SaveBuildEstimateCalc;
                    ctrl.UpdateMonthlyPayment = BuildEstimateCalcService.CalcMonthlyPayment;
                    ctrl.selectedCredit = ctrl.affordabilityCalculator
                        .CreditScore[Number(ctrl.PaymentCalculatorSettings.SelectedCreditScoreId)-1].CreditScore;
                    ctrl.UpdateMinMaxMonthlyPayment();
                };

                var setValidationRules = function () {
                    $('#payment-dialog-calculator-form').validate({
                        errorClass: "input-error",
                        rules: {
                            "calculator-cash-rebate": {
                                number: true,
                                min: 0,
                                max: 250000
                            },
                            "calculator-trade-in": {
                                number: true,
                                min: 0
                            },
                            "calculator-amount-owed": {
                                number: true,
                                min: 0,
                                max: 250000
                            },
                            "calculator-down-payment": {
                                number: true,
                                min: 0,
                                max: 250000
                            }
                        }
                    });
                };

                ctrl.$onInit = function () {
                    paymentCalcSetting();
                    setValidationRules();
                }

            }],

        templateUrl: UrlContent("/App/Common/Views/BuildEstimatePaymentCalculatorComponent.html")
    });
})(angular.module('AutoSMART.Web.Common'));

(function(module) {
    module.component('dealerratercomponent', {
        templateUrl: "/App/Common/Views/DealerRaterComponent.html",
        controller: ["$scope", "DealerRaterService", "Config", "DealerSearchService", 'VehicleDetailService', 'QuickQuoteService', '$window', 'AmplitudeService',
            function ($scope, DealerRaterService, Config, DealerSearchService, VehicleDetailService, QuickQuoteService, $window, AmplitudeService ) {
                var ctrl = this;
                ctrl.isLoading = true;
                ctrl.$onInit = function () {
                    ctrl.itemId = (($scope.$parent.$ctrl.data) ? $scope.$parent.$ctrl.data.ItemID : null);
                    if (ctrl.itemId == null && $scope.$parent.$ctrl.vehicleInfo)
                    {
                        ctrl.itemId = $scope.$parent.$ctrl.vehicleInfo.ItemID;
                    }

                    ctrl.page = 0;
                    ctrl.clientCode = ($scope.$parent.$ctrl.data) ? $scope.$parent.$ctrl.data.ClientCode : $scope.$parent.$ctrl.dedicatedDealerContent.ClientCode;
                    DealerRaterService.GetRatings(ctrl.clientCode, ctrl.page).then(function(data) {
                        ctrl.ratings = data;
                        ctrl.isLoading = false;
                    });

                    DealerSearchService.GetDealerInfo(ctrl.clientCode).then(function () {
                        ctrl.dealerInfo = DealerSearchService.DealerInfo.SearchResults[0];
                        ctrl.isLoading = false;

                        if (ctrl.itemId) {
                            VehicleDetailService.RetrieveVehicle(ctrl.itemId, true, true).then(function () {
                                handleAmplitude(VehicleDetailService.VehicleInfo);
                            });
                        }
                        else {
                            handleAmplitude(null);
                        }
                    });
                    ctrl.displayMemberCertModal = $scope.$parent.displayMemberCertModal;
                };

                var handleAmplitude = function (vehicleInfo)
                {
                    var qqstatus = Config.IsQuickQuoteEnabled && QuickQuoteService.GetQQProcessResultCookie();
                    ctrl.qqstatus = qqstatus ? 'True' : 'False';
                    var isPrefredPlus = 'True';
                    if (vehicleInfo && !vehicleInfo.IsPlusDealer) {
                        isPrefredPlus = 'False';
                    }
                    var sourcePage = '';
                    var currentUrl = window.location.href.toLowerCase();
                    if (currentUrl.includes('search')) {
                        sourcePage = 'search results page';
                    }
                    if (currentUrl.includes('details')) {
                        sourcePage = 'vehicle details page';
                    }
                    if (currentUrl.includes('/build')) {
                        sourcePage = 'contact a dealer results page';
                    }
                    if (currentUrl.includes('/dealer')) {
                        sourcePage = 'dealer page';
                    }
                    if (currentUrl.includes('search?dealerclientcode')) {
                        sourcePage = 'dealer dedicated page';
                    }
                    var vehicleType = ((vehicleInfo) ? vehicleInfo.PhysicalCondition.toLowerCase() : null);
                    var make = ((vehicleInfo) ? vehicleInfo.Make : null);
                    var model = ((vehicleInfo) ? vehicleInfo.Model : null);

                    AmplitudeService.logEvent('Dealer Reviews Viewed',
                        {
                            'source': sourcePage,
                            'vehicle type': vehicleType,
                            'make': make,
                            'model': model,
                            'dealership': ctrl.dealerInfo.OwnerName,
                            'Quick Quote Completed?': ctrl.qqstatus,
                            'Plus Dealer': isPrefredPlus
                        });
                }

                $scope.cancel = function () {
                    $scope.$parent.$parent.ratingInstance.dismiss('cancel');
                };
                ctrl.svgcontent = {
                    "TextColor": "#FFF",
                    "ShieldColor": "#FFF"
                };
                ctrl.ClientColorlessLogoUrl = Config.ClientColorlessLogoUrl;
            }]

        });
})(angular.module("AutoSMART.Web.Common"));
(function (module) {
    module.component('dealerraterratingscomponent', {
        templateUrl: "/App/Common/Views/DealerRaterRatingsComponent.html",
        controller: ["$scope", "DealerRaterService",
            function ($scope, DealerRaterService) {
                var ctrl = this;
                ctrl.$onInit = function () {
                    ctrl.clientCode = ctrl.data;
                    DealerRaterService.GetRatings(ctrl.clientCode).then(function (data) {
                        if (data.data) {
                            data = data.data;
                        }
                        ctrl.ratings = data;
                    });
                    
                };
                
            }],
            bindings: {
                data: '<'
            }
    });
})(angular.module("AutoSMART.Web.Common"));
"use strict";

angular.module('AutoSMART.Web.Search')
    .filter('phoneFormatter', function() {
            return function(phoneFormatter) {
                if (!phoneFormatter) {
                    return " ";
                }
                var digits = phoneFormatter.match(/\d/gi);
                var rawPhoneNumber = "";
                for (var i = 0; i < digits.length; i++) {
                    rawPhoneNumber += digits[i];
                }
                var areaCode = rawPhoneNumber.slice(0, 3);
                var phoneNumber = rawPhoneNumber.slice(3);

                phoneNumber = phoneNumber.slice(0, 3) + "-" + phoneNumber.slice(3);

                return "(" + areaCode + ") " + phoneNumber;
            };
        });
"use strict";

(function (module) {

    module.filter('vehiclefavoritefilter', ['VehicleFavoriteService', function(VehicleFavoriteService) {
        return function (itemId) {
            var obj = VehicleFavoriteService.GetVehicleByItemid(itemId)
            if (obj == true) {
                return 'save-icon fa fa-heart'
            }

            return 'save-icon fa fa-heart-o';
        };
    }]);
})(angular.module('AutoSMART.Web.Common'));
"use strict";

(function (module) {

    module.filter('imagehelperfilter', [ 'Config', function(Config) {
        return function (imagePath, size) {

            // use original source
            if (imagePath) {
                return imagePath;
           }



            var images = JSON.parse(Config.ImageSizes.replace(/&quot;/g, '"'));
            var selectedImageSize;
            angular.forEach(images, function (obj) {
                if (obj.ImageSizeId == size) {
                    selectedImageSize = obj;
                    return;
                }
            });

            // no image is given, use the no photo
           var noImagePath =  selectedImageSize.NoImagePath;
           if (!imagePath || 0 === imagePath.length) return Config.ImageServerWebsitePath + noImagePath;  

            
            // use the resized path 
           return Config.ImageInventoryUrl  + imagePath + selectedImageSize.EndPath;

             

        };
    }]);
     

})(angular.module('AutoSMART.Web.Common'));

 
"use strict";

(function (module) {

    module.filter('noimagehelperfilter', [ 'Config', function ( Config) {
        return function (size) {

            var images = JSON.parse(Config.ImageSizes.replace(/&quot;/g, '"'));
            var selectedImageSize;
            angular.forEach(images, function (obj) {
                if (obj.ImageSizeId == size) {
                    selectedImageSize = obj;
					return;
                }
                
            });
            
            // no image is given, use the no photo
            var noImagePath = selectedImageSize.NoImagePath;
            return Config.ImageServerWebsitePath + noImagePath;

        };
    }]);


})(angular.module('AutoSMART.Web.Common'));

 
'use strict';

(function (module) {
    module.filter('ifEmpty', function () {
        return function (input, defaultValue) {
            if (angular.isUndefined(input) || input === null || input === '') {
                return defaultValue;
            }

            return input;
        };
    });
})(angular.module('AutoSMART.Web.Common'));
'use strict';

(function(module) {
    module.filter('emptydatadash', function() {
        return function(string, appendText) {
            if (string === null || string === 'undefined') {
                return '-';
            }

            if (string.toString().length === 0) {
                return '-';
            }

            if (appendText) {
                return string + ' ' + appendText;
            }

            return string;
        };
    });
})(angular.module('AutoSMART.Web.Common'));
"use strict";
angular.module('AutoSMART.Web.HomePage', ['ui.router', 'AutoSMART.Web.Common', 'angular.filter', 'rzModule', 'ui.bootstrap', 'ngCookies', 'ngAnimate', 'angular-appinsights']);

 
"use strict";
angular.module('AutoSMART.Web.Finance', [ 'AutoSMART.Web.Common', 'angular.filter', 'rzModule', 'ui.bootstrap']);


"use strict";
angular.module('AutoSMART.Web.LendingTree', ['ui.router', 'AutoSMART.Web.Common', 'angular.filter', 'rzModule', 'ui.bootstrap', 'ngCookies', 'ngAnimate']);
"use strict";
angular.module('AutoSMART.Web.PreQualify', ['AutoSMART.Web.Common', 'angular.filter', 'rzModule', 'ui.bootstrap', 'ui.mask']);


"use strict";
angular.module('AutoSMART.QQResult', ['ui.router', 'AutoSMART.Web.Common', 'angular.filter', 'rzModule', 'ui.bootstrap', 'ngCookies', 'ui.mask']);


"use strict";
angular.module('AutoSMART.QQNoResults', ['ui.router', 'AutoSMART.Web.Common', 'angular.filter', 'rzModule', 'ui.bootstrap', 'ngCookies', 'ui.mask']);


'use strict';

(function (module) {
    module.service('CobrandClientService', ['$http', '$q', function ($http, $q) {

        var mainUrl = window.location.protocol + '//' + window.location.host + '/';

        function getCustomModules() {
            var deferred = $q.defer();

            var link = mainUrl + 'home/GetCustomModules/';
            
            var promise = $http({
                method: 'Get',
                url: link,
                headers: {
                    'Content-Type': 'application/json',
                    'Accept': '*/*',
                    'Accept-Language': 'en-US,en;q=0.8'
                }
            }).then(function (data) {
                if (data.data) {
                    data = data.data;
                }
                deferred.resolve(data);
            }).catch(function (data) {
                deferred.reject(data);
            });
            return deferred.promise;
        };

        return {
            GetCustomModules: getCustomModules
        }
    }]);

})(angular.module('AutoSMART.Web.HomePage'));
"use strict";


(function (module) {

    module.component('homepagecomponent', {        
        templateUrl: '/app/Home/views/HomePage.html',
        controller: ['$scope', '$window', 'Config', '$uibModal', 'ZipCodeService', 'LoggingService', 'GoogleAnalyticsService', '$cookies', 'insights', 'AmplitudeService',

            function ($scope, $window, Config, $uibModal, ZipCodeService, LoggingService, GoogleAnalyticsService, $cookies, insights, AmplitudeService) {
                var $ctrl = this;

                var userActionType = 22; //ApplyForLoan
                var sourceSectionID = 9; //Home Page
                var zipModalOpened = false;

                $scope.data = {newOrUsed: null}
                $scope.autoLoans = Config.IsLoanApplicationEnabled;
                $scope.isQuickQuoteEnabled = Config.IsQuickQuoteEnabled;

                $scope.goPage = function(page) {
                    var url = '';
                    switch (page) {
                    case 'newvehicle':
                        url = '/newvehicle';
                        break;
                    case 'New':
                            url = '/usedvehicle?condition=' + page;
                            AmplitudeService.logEvent('Cars Search Started',
                                {
                                    'CTA location': 'landing page CTA',
                                    'car type': page,
                                    'zip code': ZipCodeService.Location.CurrentZipCode
                                });   
                        break;
                    case 'Used':
                            url = '/usedvehicle?condition=' + page;
                            AmplitudeService.logEvent('Cars Search Started',
                                {
                                    'CTA location': 'landing page CTA',
                                    'car type': page,
                                    'zip code': ZipCodeService.Location.CurrentZipCode
                                });   
                        break;
                    case 'newused':
                        url = '/usedvehicle';
                        break;
                     case 'prequalify':
                            url = '/prequalify';
                            $ctrl.ExploreFinancingClick = GoogleAnalyticsService.ExploreFinancingClick;
                            $ctrl.ExploreFinancingClick();
                            AmplitudeService.logEvent('Quick Quote Application Started',
                                {
                                    'CTA Location': 'Home Page top CTA'
                                });

                            break;
                     case 'prequalifynew':
                            url = '/prequalify';
                            $ctrl.ExploreFinancingClick = GoogleAnalyticsService.ExploreFinancingClick;
                            $ctrl.ExploreFinancingClick();
                            AmplitudeService.logEvent('Quick Quote Application Started',
                                {
                                    'CTA Location': 'Home Page 2nd CTA'
                                });        
                        break;
                    case 'loan':
                        GoogleAnalyticsService.homeApplyLoanBtn;
                        LoggingService.LoggingUserAction(userActionType, null, sourceSectionID);
                        $window.open(Config.PreApprovalUrl.replace(/&amp;/g, '&'), '_blank');
                        break;
                    default:
                    }

                    if (url) {
                       if (!$cookies.get("user-zip")) {
                            $ctrl.openModal(url);
                        } else {
                            $window.location.href = url;
                        }
                    }
                };

                $scope.clickedSearchNewOrUsed = function() {
                    switch ($scope.data.newOrUsed) {
                        case 'NEW':
                            $scope.goPage('New');
                            break;
                        case 'USED':
                            $scope.goPage('Used');
                            break;
                        default:
                    }
                }

                $ctrl.openModal = function (redirectUrl) {
                    if (zipModalOpened) return;

                    var modalInstance = $uibModal.open({
                        component: 'zipcodemodal',
                        scope: $scope
                    });

                    zipModalOpened = true;

                    modalInstance.result.finally(function () {
                        if (!$cookies.get("user-zip")) {
                            ZipCodeService.SaveZipCode(ZipCodeService.Location.CurrentZipCode);
                        }
                        $window.location.href = redirectUrl;
                        zipModalOpened = false;
                    });

                    $scope.ZipCodeModal = modalInstance;
                };

                $ctrl.$onInit = function () {
                    insights.logEvent("Home Page load finish", { 'tenentId': Config.ClientCode, "correlationId": homepageCorrelationId });
                    AmplitudeService.logEvent('Landing Page Viewed', { 'page name': window.location.hostname, 'Landing Page Viewed': document.referrer });
                  //  console.log(amplitude.getInstance().options.deviceId);
                    
                }
            }
        ]
    });

})(angular.module('AutoSMART.Web.HomePage'));

'use strict';
(function (module) {
    module.component('heroimagecomponent', {
        templateUrl: '/app/Home/Views/HeroImageComponent.html',
        controller: ['$window', '$scope', '$interval', 'Config', function ($window, $scope, $interval, Config) {
            var $ctrl = this;
            $ctrl.HeroImages = [];
            $ctrl.currentIndex = 0;
            $ctrl.play = true;
            $ctrl.playPause = "Pause";

            var isMobile = $window.matchMedia('(max-width: 480px)');

            var intervalHeroImageAnimation = function () {
                if (!isMobile.matches) {
                    $interval(function () {
                        if ($ctrl.play == true) {
                            $ctrl.HeroImages[$ctrl.currentIndex].visible = false; // make the current image visible
                            $ctrl.currentIndex < $ctrl.HeroImages.length - 1 ? $ctrl.currentIndex++ : $ctrl.currentIndex = 0;
                            $ctrl.HeroImages[$ctrl.currentIndex].visible = true; // make the current image visible
                        }
                    }, 5000);
                } else {
                    var randomIndex = Math.floor(Math.random() * $ctrl.HeroImages.length);
                    $ctrl.HeroImages[randomIndex].visible = true;
                }
            };

            $ctrl.$onInit = function () {
                var index = 0;
                var retina = ($window.devicePixelRatio > 1.5) ? "@2x.jpg" : ".jpg";
                 
                angular.forEach(Config.HeroImages.split(','), function (data) {
                    if (data !== "") {
                        var img = data.replace(/.jpg/g, retina);
                        $ctrl.HeroImages.push(
                            {
                                imgsrc: Config.HeroImageFilePath + "/AutoSMART/HeroImages/CombinedAS4/" + img,
                                visible: index === 0 ? true :false
                            });
                        index = index + 1;
                    }
                });
                intervalHeroImageAnimation();
            };

            $ctrl.togglePlayPause = function () {
                if ($ctrl.playPause == "Play") {
                    $ctrl.playPause = "Pause";
                    $ctrl.play = true;
                } else {
                    $ctrl.playPause = "Play";
                    $ctrl.play = false; 
                }
            }
        }]
    });
})(angular.module('AutoSMART.Web.HomePage'));
'use strict';
(function (module) {
    module.component('custommodulescomponent', {
        templateUrl: '/app/Home/Views/CustomModulesComponent.html',
        controller: ['$scope', '$location', '$window', 'Config', '$sce', 'CobrandClientService', function ($scope, $location, $window, Config, $sce, CobrandClientService) {
            var ctrl = this;
            var mainUrl = window.location.protocol + '//' + window.location.host + '/';

            ctrl.CustomModules = {
                LargeModule: {},
                SmallLeft: {},
                SmallRight: {}
            };

            $scope.hasModules = false;

            ctrl.$onInit = function () {
                CobrandClientService.GetCustomModules().then(function (data) {
                    if (data.data) {
                        data = data.data;
                    }
                    if (data) {
                        $scope.hasModules = true;
                        setUpModuleData(data);
                    }
                });
            };

            var setUpModuleData = function(data) {
                angular.forEach(data, function (module) {
                    if (module.ImageContentId) {
                        module.ImageUrl = mainUrl + "Content/Image/" + module.ImageContentId;
                    }

                    if (module.ClientModuleTypeId === 1) {
                        ctrl.CustomModules.SmallLeft = module;
                    }
                    else if (module.ClientModuleTypeId === 2) {
                        ctrl.CustomModules.SmallRight = module;
                    }
                    else if (module.ClientModuleTypeId === 3) {
                        ctrl.CustomModules.LargeModule = module;
                        if (ctrl.CustomModules.LargeModule.Title === null ) {
                            ctrl.CustomModules.LargeModule.AltTag = "Custom Module";
                        } else {
                            ctrl.CustomModules.LargeModule.AltTag = ctrl.CustomModules.LargeModule.Title;
                        }
                        
                    }
                });
            };

            ctrl.renderHtml = function(html) {
                return $sce.trustAsHtml(html);
            };
        }]
    });
})(angular.module('AutoSMART.Web.HomePage'));
'use strict';
(function(module) {
    module.component('dealermakescomponent', {
        templateUrl: '/app/Home/Views/DealerMakesComponent.html',
        controller: ['$scope', '$window','Config','VehicleMakesService', function ($scope, $window,Config, VehicleMakesService) {
            var ctrl = this;
            var specialtyVehicles = JSON.parse(Config.SpecialtyVehicleTypes.replace(/&quot;/g, '"'));
            ctrl.FindADealerMakeSelect = [{ 'name': 'Select a Make', 'value': '' }];

            var populateMakes = function(data) {

                angular.forEach(data, function (make) {
                    var makeObj = {
                        'name': make.Make,
                        'value': make.Make,
                        'IsSpecialtyType': false
                    };
                    ctrl.FindADealerMakeSelect.push(makeObj);
                });

                angular.forEach(specialtyVehicles, function(specialtyVehicle) {
                    var makeObj = {
                        'name': specialtyVehicle.Key,
                        'value': specialtyVehicle.SearchValue,
                        'IsSpecialtyType': true
                };
                    ctrl.FindADealerMakeSelect.push(makeObj);
                });

                //add Independent Dealer Option
                var independentDealerMakeObj = {
                    'name': "Independent Dealers Only",
                    'value': "Independent Dealers Only",
                    'IsSpecialtyType': false
                };
                ctrl.FindADealerMakeSelect.push(independentDealerMakeObj);

                $scope.selectedMake = ctrl.FindADealerMakeSelect;
                ctrl.selectedMake = ctrl.FindADealerMakeSelect[0].name;
                ctrl.isSpecialtyType = "";
                ctrl.selectedMakeValue = '';
            };

            ctrl.DealerSearchClicked = function()
            {
                if (ctrl.isSpecialtyType) {
                    return $window.location.href = "/Dealer?ItemType=" + encodeURIComponent(ctrl.selectedMakeValue);
                } 
               // amplitude.getInstance().logEvent('Clicked Home Page - Dealer Search', { 'carMake': ctrl.selectedMakeValue, 'cuCode': Config.ClientCode});

                return $window.location.href = (ctrl.selectedMakeValue != '')? "/Dealer?Makes=" + encodeURIComponent(ctrl.selectedMakeValue):"/Dealer";  
            }

            ctrl.$onInit = function() {
                VehicleMakesService.GetInventoryMakes().then(function (data) {
                    if (data.data) {
                        data = data.data;
                    }
                    populateMakes(data);
                    $scope.selectedMake = ctrl.FindADealerMakeSelect[0];
                });
            };

            ctrl.setSelected = function (make) {
                var makeIndex = 0;
                for (var i = ctrl.FindADealerMakeSelect.length - 1; i > 0; i--) {
                    if (ctrl.FindADealerMakeSelect[i].name === make) {
                        makeIndex = i;
                        break;
                    }
                }

                ctrl.selectedMake = ctrl.FindADealerMakeSelect[makeIndex].name;
                ctrl.isSpecialtyType = ctrl.FindADealerMakeSelect[makeIndex].IsSpecialtyType;
                ctrl.selectedMakeValue = ctrl.FindADealerMakeSelect[makeIndex].value;
            }
        }]
    });
})(angular.module('AutoSMART.Web.HomePage'));

"use strict";
angular
    .module('AutoSMART.Web.CertificateProcess', ['ui.router', 'ui.bootstrap', 'ngAria'])
    .config(['$urlRouterProvider', '$locationProvider', '$httpProvider', function ($urlRouterProvider, $locationProvider, $httpProvider) {
        $locationProvider.html5Mode({ enabled: true, rewriteLinks: false, requireBase: false });
    }]);

"use strict";

(function (module) {

    module.service('AnchorScrollService', function () {
    this.getYPosition =
    function getYPosition(eID) {
        var elm = document.getElementById(eID);
        var y = elm.offsetTop;
        var node = elm;
        while (node.offsetParent && node.offsetParent != document.body) {
            node = node.offsetParent;
            y += node.offsetTop;
        } return y;
    }

    this.currentYPosition = 
    function currentYPosition() {
        // Firefox, Chrome, Opera, Safari
        if (self.pageYOffset) return self.pageYOffset;
        // Internet Explorer 6 - standards mode
        if (document.documentElement && document.documentElement.scrollTop)
            return document.documentElement.scrollTop;
        // Internet Explorer 6, 7 and 8
        if (document.body.scrollTop) return document.body.scrollTop;
        return 0;
    }

    this.scrollTo = function (eID, stopY) {

        var startY = this.currentYPosition();
        
        var distance = stopY > startY ? stopY - startY : startY - stopY;
        if (distance < 100) {
            scrollTo(0, stopY); return;
        }
        var speed = Math.round(distance / 100);
        if (speed >= 20) speed = 20;
        var step = Math.round(distance / 100);
        var leapY = stopY > startY ? startY + step : startY - step;
        var timer = 0;
        if (stopY > startY) {
            for (var i = startY; i < stopY; i += step) {
                setTimeout("window.scrollTo(0, " + leapY + ")", timer * speed);
                leapY += step; if (leapY > stopY) leapY = stopY; timer++;
            } return;
        }
        for (var i = startY; i > stopY; i -= step) {
            setTimeout("window.scrollTo(0, " + leapY + ")", timer * speed);
            leapY -= step; if (leapY < stopY) leapY = stopY; timer++;
        }
    };

});

})(angular.module('AutoSMART.Web.Search'));
"use strict";

(function (module) {

    module.service("DealerService", ['Config', '$http', '$q', function (Config, $http, $q) {

    var saveMemberCertificateInfo = function (memberCertificate) {
        var deferred = $q.defer();
       
        $http.post(Config.BaseDirectory + '/CertificateService/SaveMemberCertificate', { memberCertificate: memberCertificate })
            .then(function (result) {
                deferred.resolve(result.data);
            }, function (errorResult) {               
                deferred.reject(errorResult);
            });
        return deferred.promise;
    };

    return {        
        SaveMemberCertificateInfo: saveMemberCertificateInfo
    }
}]);
})(angular.module('AutoSMART.Web.Search'));
"use strict";

(function (module) {

    module.service("TrimService", ['Config', '$http', '$q', function (Config, $http, $q) {

        var retrieveBuildVehicleStyle = function (year, make, model) {
            var deferred = $q.defer();
            var url = Config.BaseDirectory + "/TrimService/RetrieveBuildVehicleStyle";

            $http({
                url: url,
                method: "GET",
                params: {
                    year: year,
                    make: encodeURIComponent(make),
                    model: encodeURIComponent(model)
                }
            })
                .then(function (terms) {
                    deferred.resolve(terms.data);
                }, function (errorResult) {
                    deferred.reject(errorResult);
                });
            return deferred.promise;
        };

        var retrieveSpecificTrim = function (styleId) {
            var deferred = $q.defer();

            var url = Config.BaseDirectory + "/TrimService/RetrieveSpecificTrim";

            $http({
                url: url,
                method: "GET",
                params: {
                    styleId: styleId
                }
            })
                .then(function (terms) {
                    deferred.resolve(terms.data);
                }, function (errorResult) {
                    deferred.reject(errorResult);
                });
            return deferred.promise;
        };

        return {
            RetreiveSpecificTrim: retrieveSpecificTrim,
            RetrieveBuildVehicleStyle: retrieveBuildVehicleStyle
        }
    }]);

})(angular.module('AutoSMART.Web.Search'));

//Share data between controller
"use strict";

(function (module) {

    module.factory('Data', ['Config', '$anchorScroll', function (Config, $anchorScroll) {
    var retrieveUsedCarTemplate = function () {
        var orderid = 0;
        var returnTemplates = new Array();        
        returnTemplates.push({ site: 'TradeIn', url: Config.BaseDirectory + '/App/CertificateProcess/Views/TradeIn.html', active: true, orderId: orderid++ });
 
        if (Config.IsLoanApplicationEnabled) {
            returnTemplates.push({ site: 'PreApproval', url: Config.BaseDirectory + '/App/CertificateProcess/Views/PreApproval.html', active: true, orderId: orderid++ });
        }
        returnTemplates.push({ site: 'Summary', url: Config.BaseDirectory  + '/App/CertificateProcess/Views/Summary.html', active: true, orderId: orderid++ });
        return returnTemplates
    }

    var retrieveNewCarTemplate = function () {

        var orderid = 0;
        var returnTemplates = new Array();       
        returnTemplates.push({site: 'Trim', url: Config.BaseDirectory + '/App/CertificateProcess/Views/Trim.html', active: true, orderId: orderid++});
        returnTemplates.push({ site: 'TradeIn', url: Config.BaseDirectory + '/App/CertificateProcess/Views/TradeIn.html', active: true, orderId: orderid++ });
        returnTemplates.push({ site: 'Dealer', url: Config.BaseDirectory + '/App/CertificateProcess/Views/Dealer.html', active: true, orderId: orderid++ });

        if (Config.IsLoanApplicationEnabled) {
            returnTemplates.push({ site: 'PreApproval', url: Config.BaseDirectory + '/App/CertificateProcess/Views/PreApproval.html', active: true, orderId: orderid++});
        }
        returnTemplates.push({site: 'Summary', url: Config.BaseDirectory + '/App/CertificateProcess/Views/Summary.html', active: true, orderId: orderid++});
        return returnTemplates;
    }

    return {     
        RetrieveNewCarTemplate: retrieveNewCarTemplate,
        RetrieveUsedCarTemplate: retrieveUsedCarTemplate
    };
}])})(angular.module('AutoSMART.Web.Search'));
"use strict";

(function (module) {

    module.service("InfoService", ['Config', '$http', '$q', function (Config, $http, $q) {

        var retrieveLoanTerms = function () {
            var deferred = $q.defer();

            var link = Config.BaseDirectory + '/ClientInfoService/RetrieveLoanTerms';

            $http.get(link)
                .then(function (terms) {
                    deferred.resolve(terms.data);
                }, function (errorResult) {
                    deferred.reject(errorResult);
                });
            return deferred.promise;
        };

        var retrieveReferralContactType = function () {
            var deferred = $q.defer();

            var link = Config.BaseDirectory + '/ClientInfoService/RetrieveReferralContactType';

            $http.get(link)
                .then(function (terms) {
                    deferred.resolve(terms.data);
                }, function (errorResult) {
                    deferred.reject(errorResult);
                });
            return deferred.promise;
        };

        return {
            RetrieveLoanTerms: retrieveLoanTerms,
            RetrieveReferralContactType: retrieveReferralContactType
        }
    }]);
})(angular.module('AutoSMART.Web.Search'));
"use strict";

(function (module) {

    module.service("InventoryService", ['Config', '$http', '$q', function (Config, $http, $q) {
        
    var retrieveVehicleInfo = function (itemid) {
        var deferred = $q.defer();              
        var url = Config.BaseDirectory + '/InventoryService/RetrieveVehicleInfo'
        $http({
            url: url,
            method: "GET",
            params: { itemid: itemid }
        })
                .then(function (terms) {
                    deferred.resolve(terms.data);
                }, function (errorResult) {
                    deferred.reject(errorResult);
                });
        return deferred.promise;
     
    };
        return {
            RetrieveVehicleInfo: retrieveVehicleInfo
        }
    }
]);
})(angular.module('AutoSMART.Web.Search'));
//"use strict";

 
//(function (module) {

//    module.controller('mainCtrl', ['$scope', 'Data', 'DealerService', '$window', '$sce',
//                            'PreApprovalService', '$location', '$timeout', 'TrimService',
//                            '$anchorScroll', 'Config', 'InventoryService', '$uibModal',
//                            'KBBService', 'InfoService' ,'AnchorScrollService',
//    function ($scope,
//        Data,
//        DealerService,
//        $window,
//        $sce,
//        PreApprovalService,
//        $location,
//        $timeout,
//        TrimService,
//        $anchorScroll,
//        Config,
//        InventoryService,
//        $uibModal,
//        KBBService,
//        InfoService,
//        AnchorScrollService
//        ) {
//        var baseDirectory = "";
//        var templateList = new Array();
//        $scope.Templates = new Array();
//        $scope.vehicleInfo = {};
//        $scope.IsNewVehicle = true;
//        $scope.ChromeStyleID = 0;
//        var vehicleYear = "";
//        var vehicleMake = "";
//        var vehicleModel = "";
//        var vehicleTrim = "";
//        var templateYPositionArray = { key: undefined };

//        $scope.MemberCertificate = {};
//        $scope.User = {};
//        $scope.scrollContainerParent = undefined;
//        $scope.scrollContainerChild = undefined;
//        $scope.IsMobile = Config.IsMobileVersionCode;

//        var displaySummarySection = function () {
//            for (var i in $scope.Templates) {
//                if ($scope.Templates[i].active == true) {
//                    return;
//                }
//            }
//            for (var i in $scope.Templates) {
//                if ($scope.Templates[i].site === 'Summary') {
//                    $scope.Templates[i].active = true;
//                }
//            }
//        }
//        var intialLoad = true;
//        var moveNextSection = function (orderId) {
//            //NOTE: Wrapping the contents of this function allows the partials to fully update before the moveNextSection actually occurs.
//            $timeout(function () {
//                setAllSectionInactive();
//                var nextOrderId = orderId + 1;
//                var nextSite = templateList[nextOrderId].site;
//                while (templateList[nextOrderId].hasSelection) {
//                    nextOrderId += 1;
//                    nextSite = templateList[nextOrderId].site;
//                }

//                for (var i in $scope.Templates) {
//                    if ($scope.Templates[i].site === nextSite) { //If selected site is found
//                        //Case when all is display and user modify 
//                        if ($scope.Templates.length == templateList.length) {
//                            displaySummarySection();
//                            scrollToTemplate(nextSite, false);
//                            return;
//                        }
//                        $scope.Templates[i].active = true;
//                        if (!intialLoad) {
//                            scrollToTemplate(nextSite, true);
//                        }
                   
//                        intialLoad = false;
//                        return;
//                    }
//                }

//                var exists = $.grep($scope.Templates,
//                    function (t) {
//                        return t.site === templateList[nextOrderId].site;
//                    });
//                if (exists.length === 0) {
//                    //templateList[nextOrderId].active = true;
//                    $scope.Templates.push(templateList[nextOrderId]);
//                    $scope.length += 1;
//                }

//                //$timeout allows the template to load in order to retrieve the height of the template.
//                $timeout(function () {
//                    scrollToTemplate(nextSite, true);

//                },
//                    100);

//                displaySummarySection();

//            });

//        }
         
//        function scrollToTemplate(nextSite, animateHeight, disableScroll) {  

//            var templateid = nextSite;

//            if (templateid in templateYPositionArray) {
//                stopY = templateYPositionArray[templateid]
//            }
//            else {
//                stopY = AnchorScrollService.getYPosition(templateid);
//                templateYPositionArray[templateid] = stopY;
//            }
//            // header offset 
//            var stickyHeaderOffset = -50;
//            AnchorScrollService.scrollTo(templateid, stopY + stickyHeaderOffset);
//        }

//        $scope.LoadReturnUrl = function () {
//            if (Config.ConfirmationReturnUrl && Config.ConfirmationReturnUrl.length > 0) {

//                //casting to textarea conent fully decodes the encoded confirmationurl
//                var elem = document.createElement('textarea');
//                elem.innerHTML = Config.ConfirmationReturnUrl;
//                var url = elem.value;
//                var splitUrl = url.split('&model=');

//                url = splitUrl[0];

//                if (splitUrl.length > 1) {
//                    var model = splitUrl[1];
//                    var hashIndex = model.indexOf('#');
//                    if (hashIndex > -1) {
//                        model = splitUrl[1].substring(0, splitUrl[1].indexOf('#'));
//                }
//                    url = url + '&model=' + model;
//                }

//                $window.location.href = url;
//            }
//        };

//        $scope.CreditUnionName = Config.CreditUnionName;

//        var init = function () {
//            var params = $location.search();
//            if (params['chromestyleid']) {
//                $scope.ChromeStyleID = params['chromestyleid'];
//            }

//            $scope.StickyHeaderUrl = Config.BaseDirectory + '/App/CertificateProcess/Views/StickyHeader.html';
//            $scope.IntroductionUrl = Config.BaseDirectory + '/App/CertificateProcess/Views/Introduction.html';

//            if (params['itemid']) {
//                templateList = Data.RetrieveUsedCarTemplate();
//                $scope.Templates.push(templateList[0]);
//                InventoryService.RetrieveVehicleInfo(parseInt(params['itemid']))
//                    .then(function (data) {
//                        $scope.vehicleInfo = data;
//                        if ($scope.vehicleInfo.PhysicalConditionId == 2) {
//                            $scope.vehicleInfo.BaseMSRP = $scope.vehicleInfo.Price;
//                            $scope.IsNewVehicle = false;
//                        } else {
//                            vehicleYear = $scope.vehicleInfo.Year;
//                            vehicleMake = $scope.vehicleInfo.MakeName;
//                            vehicleModel = $scope.vehicleInfo.ModelName;
//                            vehicleTrim = $scope.vehicleInfo.TrimName;
//                            $scope.vehicleInfo.VendorStyleID = 0;


//                            $scope.UpdateKBBUrl();

//                            //Get BaseMSRP
//                            RetrieveSpecificTrim($scope.vehicleInfo.VendorStyleID);
//                            RetrieveTrimOptions();
//                        }
//                        $scope.MemberCertificate.CliendIds = new Array();
//                        $scope.MemberCertificate.CliendIds.push($scope.vehicleInfo.ClientID);
//                    });

//                $scope.dealer = [Config.UsedVehicleDealerName];
//                return;
//            }

//            if (params['year']) {
//                vehicleYear = params['year'];
//            }
//            if (params['make']) {
//                vehicleMake = params['make'];
//            }
//            if (params['model']) {
//                vehicleModel = params['model'];
//            }

//            templateList = Data.RetrieveNewCarTemplate();
//            //Push only the 1st template to get the correct size
//            $scope.Templates.push(templateList[0]);

//            RetrieveTrimOptions();
//            RetrieveDealers();

//        }

//        $scope.UpdateKBBUrl = function () {
//            if ($scope.selectedTrim) {
//                KBBService.RetrieveKBBIdFromCID($scope.selectedTrim.ChromeStyleID)
//                    .then(function (data) {
//                        KBBService.SetVID((data['kbbid'] ? data['kbbid'] : '000000'));
//                        KBBService.SetZipCode(Config.KBBZipCode);
//                        $scope.KBBUrl = $sce.trustAsResourceUrl(KBBService.ReloadURL());
//                    });
//            }
//            else {                
//                KBBService.RetrieveKBBId(vehicleYear, vehicleMake, vehicleModel, vehicleTrim)
//                    .then(function (data) {
//                        KBBService.SetVID((data['kbbid'] ? data['kbbid'] : '000000'));
//                        KBBService.SetZipCode(Config.KBBZipCode);
//                        $scope.KBBUrl = $sce.trustAsResourceUrl(KBBService.ReloadURL());
//    });
//            }


//        }

//        var RetrieveTrimOptions = function () {

//            TrimService.RetrieveBuildVehicleStyle(vehicleYear, vehicleMake, vehicleModel)
//                .then(function (data) {
//                    for (var i in data) {
//                        if (data[i].IsBaseTrim == 1) {
//                            var trimOption = {
//                                TrimName: data[i].Trim.replace(/\//g, '/ '),
//                                ChromeStyleID: data[i].ChromeStyleId,
//                                MakeName: data[i].Make,
//                                ModelName: data[i].Model,
//                                Year: data[i].Year,
//                                BaseMSRP: data[i].BaseMSRP,
//                                HwyMPG: data[i].HwyMPG,
//                                CityMPG: data[i].CityMPG
//                            }

//                            if (data[i].ImageUrl.length === 0) {
//                                trimOption.ImageUrl = "/Content/images/img-NoVehiclePhoto-BuildTrim.png";
//                            } else {
//                                trimOption.ImageUrl = data[i].ImageUrl;
//                            }
//                            trimOption.Exterior = data[i].Exterior.replace("Wheels: ", "");
//                            trimOption.Transmission = data[i].Transmission.split(" -inc:")[0];
//                            trimOption.Engine = data[i].Engine.split(" -inc:")[0];
//                            $scope.TrimOptions.push(trimOption);
//                        }
//                    }

//                    if ($scope.ChromeStyleID) {
//                        updateSelectedTrim();
//                        return;
//                    }

//                    if ($scope.vehicleInfo && $scope.vehicleInfo.ItemId) {
//                        if ($scope.TrimOptions.length === 0)
//                            return;

//                        SearchMatchingTrim();
//                        return;
//                    }

//                    GetBaseTrim();
//                });
//        }

//        var SearchMatchingTrim = function () {
//            for (var i = 0; i < $scope.TrimOptions.length; i++) {
//                if ($scope.TrimOptions[i].TrimName == $scope.vehicleInfo.TrimName) {
//                    $scope.vehicleInfo.BaseMSRP = $scope.TrimOptions[i].BaseMSRP;
//                }
//            }
//        }

//        var RetrieveSpecificTrim = function (styleId) {
//            TrimService.RetreiveSpecificTrim(styleId)
//                .then(function (data) {
//                    if (data && data.length > 0) {
//                        $scope.vehicleInfo.BaseMSRP = data[0].BaseMSRP;
//                    }
//                });
//        }

//        var RetrieveDealers = function () {
//            var dealers = JSON.parse(Config.Dealers.replace(/&quot;/g, '"'));
//            var dealerInfo = {};
//            var allDealers = [];
//            var vehicleInStockTotal = 0;
//            var distanceText = [];
//            var starRatingList = {};
//            var dealerCount = 0;
//            var singleDealer = false;
//            for (var i = 0; i < dealers.Owners.length; i++) {
//                dealerCount += 1;
//                if (Math.round(dealers.Owners[i].Distance == 1)) {
//                    distanceText[i] = "Mile Away";
//                } else {
//                    distanceText[i] = "Miles Away";
//                };

//                if (dealers.Owners[i].OwnerRating > 0 && dealers.Owners[i].OwnerRating != null) {
//                    var totalIconCount = 5;
//                    var currentIconCount = 1;
                            
//                    for (; currentIconCount <= (dealers.Owners[i].OwnerRating / 1) ; currentIconCount++)
//                    {
//                        starRatingList[currentIconCount] = { starclass: "fa fa-star" };
                        
//                    };
//                    if ((dealers.Owners[i].OwnerRating % 1) != 0)
//                    {
//                        starRatingList[currentIconCount] = { starclass: "fa fa-star-half-full" };
//                        currentIconCount++;
//                    };
//                    if (dealers.Owners[i].OwnerRating != totalIconCount)
//                    {
//                        for (;currentIconCount <= totalIconCount; currentIconCount++)
//                        {
//                            starRatingList[currentIconCount] = { starclass: "fa fa-star-o" };
//                        };
//                    };
//                };

//                dealerInfo[i] = {
//                    DealerName: dealers.Owners[i].OwnerName,
//                    StockCount: dealers.Owners[i].InventoryCount,
//                    Distance: Math.round(dealers.Owners[i].Distance),
//                    DistanceText: distanceText[i],
//                    ShowDealerRating: dealers.Owners[i].OwnerRating != null && dealers.Owners[i].OwnerRating > 0 ? true : false,
//                    StarRatingList: starRatingList,
//                    DealerId: dealers.Owners[i].DealerId,
//                    ClientId: dealers.Owners[i].ClientId
//                };
//                starRatingList = {};
//                allDealers[i] = dealers.Owners[i].OwnerName;
//                vehicleInStockTotal += dealers.Owners[i].InventoryCount;
//            }
//            if (dealerCount == 1) {
//                singleDealer = true;
//            }
//            $scope.AllDealers = allDealers;
//            $scope.DealerInformation = dealerInfo;
//            $scope.VehicleInStockTotal = vehicleInStockTotal;
//            $scope.SingleDealer = singleDealer;
//        }

//        var updateSelectedTrim = function () {
//            for (var i in $scope.TrimOptions) {
//                if ($scope.ChromeStyleID == $scope.TrimOptions[i].ChromeStyleID) {
//                    $scope.trim = $scope.TrimOptions[i].TrimName;
//                    $scope.vehicleInfo = {
//                        Year: $scope.TrimOptions[i].Year,
//                        MakeName: $scope.TrimOptions[i].MakeName,
//                        ModelName: $scope.TrimOptions[i].ModelName,
//                        TrimName: $scope.TrimOptions[i].TrimName,
//                        ChromeStyleID: $scope.TrimOptions[i].ChromeStyleID,
//                        BaseMSRP: $scope.TrimOptions[i].BaseMSRP
//                    }
//                }
//            }
//            // Below code is used to bypass trim selection section if user selected a trim in the research page
//            //for (var i in $scope.Templates) {
//            //    if ($scope.Templates[i].site === 'Trim') {
//            //        $scope.Templates[i].active = false;
//            //        var orderid = $scope.Templates[i].orderId;
//            //        moveNextSection(orderid);
//            //        return;
//            //    }
//            //}
//        }

//        var GetBaseTrim = function () {
//            var trimOption = $scope.TrimOptions[0];
//            var length = $scope.TrimOptions.length;
//            $scope.vehicleInfo = {
//                Year: trimOption.Year,
//                MakeName: trimOption.MakeName,
//                ModelName: trimOption.ModelName,
//                TrimName: trimOption.TrimName,
//                ChromeStyleID: trimOption.ChromeStyleID,
//                BaseMSRP: trimOption.BaseMSRP
//            }
            
//            $scope.selectedTrim = trimOption;
//            $scope.UpdateKBBUrl();
//        }

//        init();


//        var modifySelectedSection = function (orderId) {
//            setAllSectionInactive();
//            $scope.Templates[orderId].active = true;

//            $timeout(function () {
//                var animateHeight = ($scope.Templates.length === templateList.length) ? false : true;
//                scrollToTemplate($scope.Templates[orderId].site, animateHeight, true);
//            }, 100);

//        }

//        var setAllSectionInactive = function () {
//            for (var i in $scope.Templates) {
//                $scope.Templates[i].active = false;
//            }
//        }



//        //Trim     
//        $scope.trim = null;
//        $scope.selectedTrim = null;
//        $scope.TrimOptions = new Array();

//        $scope.selectTrim = function (answer, orderId) {
//            $scope.selectedTrim = answer;
//            $scope.trim = answer.TrimName;
//            $scope.vehicleInfo.TrimName = answer.TrimName;
//            $scope.vehicleInfo.BaseMSRP = answer.BaseMSRP;
//            $scope.UpdateKBBUrl();
//            templateList[orderId].hasSelection = true;
//            moveNextSection(orderId);
//        };

//        $scope.modifyTrim = function (orderId) {
//            modifySelectedSection(orderId);
//        };

//        //Trade In
//        $scope.tradeIn = null;
//        $scope.tradeInDisplay = function (isActive, orderId) {
//            switch (isActive) {
//                case 1: //modifySelectedSection button selected                
//                    modifySelectedSection(orderId);
//                    break;
//                case 3: //display the Summary with Yes                
//                    $scope.tradeIn = 'Yes';
//                    //$window.open('http://sdcm.syndication.kbb.com/?CUDW');
//                    $window.open(Config.KBBUrl);
//                    templateList[orderId].hasSelection = true;
//                    moveNextSection(orderId);
//                    break;
//                case 4: //display the Summary with No                
//                    $scope.tradeIn = 'No';
//                    templateList[orderId].hasSelection = true;
//                    moveNextSection(orderId);
//            }
//        }

//        //Dealer 

//        $scope.selectAllDealer = function (dealer, orderId) {
//            $scope.displayDealersName = '';
//            for (var i = 0; i < dealer.length; i++) {
//                $scope.displayDealersName += i < dealer.length - 1 ? dealer[i] + "; " : dealer[i];
//            }
//            $scope.dealer = dealer;
//            $scope.MemberCertificate.CliendIds = new Array();
//            angular.forEach($scope.DealerInformation,
//                function (value) {
//                    $scope.MemberCertificate.CliendIds.push(value.ClientId);
//                });
//            templateList[orderId].hasSelection = true;
//            moveNextSection(orderId);
//        }

//        $scope.selectDealer = function (dealer, orderId, clientId) {
//            $scope.dealer = new Array();
//            $scope.dealer.push(dealer);
//            $scope.displayDealersName = dealer;
//            $scope.MemberCertificate.CliendIds = new Array();
//            $scope.MemberCertificate.CliendIds.push(clientId);
//            templateList[orderId].hasSelection = true;
//            moveNextSection(orderId);
//        }

//        $scope.modifyDealer = function (orderId) {
//            modifySelectedSection(orderId);
//        }

//        $scope.openLeadForm = function () {
//            Global.NoThanksOptOut();
//            $('#RequestQuoteForm').modal("show");
//        }
//        // Pre Approval
//        $scope.PreApprovalLink = $sce.trustAsResourceUrl(PreApprovalService.getPreApprovalLink());
//        $scope.selectPreApproval = function (preApproval, orderId) {
//            $scope.preApproval = preApproval;
//            if (preApproval == 'Yes') {
//                $scope.needToSubmit = true;
//                $scope.selectedView = 3;
//                $window.open($scope.PreApprovalLink, '_blank');
//            } else {
//                $scope.selectedView = 3;
//            }
//            templateList[orderId].hasSelection = true;
//            moveNextSection(orderId);
//        }
//        $scope.goNext = function () {
//            $scope.selectedView = 3;
//            $scope.hasSubmitted = true;
//            moveNextSection(orderId);
//        }
//        $scope.close = function () {
//            $scope.preApproval = $scope.oldPreApproval;
//            $scope.needToSubmit = null;
//            $scope.selectedView = 3;
//        }

//        $scope.modifyPreApproval = function (orderId) {
//            $scope.oldPreApproval = $scope.preApproval;
//            $scope.selectedView = 1;
//            modifySelectedSection(orderId);
//        }

//        $scope.openCalcModal = function () {
//            $scope.IsNewCar = false;
//            var modalInstance = $uibModal.open({
//                controller: 'CalculatorCtrl',
//                templateUrl: Config.BaseDirectory + '/App/CertificateProcess/Views/Calculator.html',
//                windowClass: 'calcDialog',
//                scope: $scope
//            });

//            modalInstance.result.then(function (data) {
//                console.log(data);
//            });
//        }

//        //summary
//        $scope.BarCodeValue = Config.ConfirmationCode;
//        $scope.BarCodeUrl = Config.ConfirmationBarCodeUrl;
//        $scope.IsLoanApplicationEnabled = Config.IsLoanApplicationEnabled;
//        $scope.ContactOptions = new Array();
//        InfoService.RetrieveReferralContactType()
//            .then(function (data) {

//                angular.forEach(data,
//                    function (contact) {
//                        $scope.ContactOptions.push({
//                            name: contact.ReferralContact,
//                            value: contact.ReferralContactTypeID
//                        });
//                    });

//                $scope.ContactOptions.Selected = $scope.ContactOptions[0];
//            });

//        $scope.ShowConfirmationModal = "";


//        $scope.submitButtonClicked = function () {
//            $scope.submitClicked = true;
//        }

//        var setPreapproved = function (){
//            if (!$scope.preApproval) {
//                $scope.MemberCertificate.WebUserPreapproved = null;
//                return;
//            }
//            $scope.MemberCertificate.WebUserPreapproved = ($scope.preApproval.toLowerCase() === 'yes' ? true : false);
//        }

//        $scope.submit = function (form) {
//            if (form.$valid) {
//                $scope.processingCertificate = true;

//                //Contact
//                $scope.MemberCertificate.WebUserContact = $scope.User.email;
//                $scope.MemberCertificate.EmailAddress = $scope.User.email;
//                $scope.MemberCertificate.WebUserName = $scope.User.fullName;
//                $scope.MemberCertificate.ReferralContactTypeID = $scope.ContactOptions.Selected.value;
//                ////User Selection
//                $scope.MemberCertificate.ReferralDate = new Date();
//                $scope.MemberCertificate.TradeIn = ($scope.tradeIn.toLowerCase() === 'yes' ? true : false);
//                setPreapproved();                
//                ////Vehicle Info
//                $scope.MemberCertificate.ItemId = $scope.vehicleInfo.ItemId;
//                $scope.MemberCertificate.Year = $scope.vehicleInfo.Year;
//                $scope.MemberCertificate.Make = $scope.vehicleInfo.MakeName;
//                $scope.MemberCertificate.Model = $scope.vehicleInfo.ModelName;
//                $scope.MemberCertificate.Trim = $scope.trim;
//                $scope.MemberCertificate.ItemName = $scope.vehicleInfo.Year + ' ' + $scope.vehicleInfo.MakeName + ' ' + $scope.vehicleInfo.ModelName;
//                $scope.MemberCertificate.VIN = $scope.vehicleInfo.VIN;
//                $scope.MemberCertificate.StockNumber = $scope.vehicleInfo.StockNumber;
//                $scope.MemberCertificate.ChromeStyleId = $scope.vehicleInfo.ChromeStyleID;
//                /////ReferralInfo
//                $scope.MemberCertificate.ReferralType = ($scope.vehicleInfo.ItemId) ? ($scope.vehicleInfo.PhysicalConditionId === 1 ? "New" : "Used") : "New";
//                $scope.MemberCertificate.LeadTypeId = 4; //Lead Type is always AutoSmart/X

//                var expireDate = new Date();
//                expireDate.setDate(expireDate.getDate() + 7);
//                $scope.MemberCertificate.ValidThrough = expireDate;
//                $scope.MemberCertificate.ClientWebSiteID = Config.ClientWebSiteID;
//                $scope.MemberCertificate.SourcePlatformId = (Config.IsMobileVersionCode) ? 2 : 1;
//                $scope.MemberCertificate.ItemTypeId = 5; //Vehicle
//                $scope.MemberCertificate.CobrandClientId = Config.CobrandClientId;
//                $scope.MemberCertificate.CertificateCode = Config.ConfirmationCode;
//                $scope.MemberCertificate.WebUserLanguageLocale = 'en-US';
//                $scope.MemberCertificate.WebUserCopy = true; //Always send a confirmation to the member
//                $scope.MemberCertificate.ReturnUrl = window.location.href;

//                DealerService.SaveMemberCertificateInfo($scope.MemberCertificate).then(function (result) {
//                    //$('#getCertificateModal').modal("show");
                    
//                    var url = $location.protocol() + '://' + location.host + $location.path() + '/membercertificate?membercertificateguid=' + result.CertificateGuid + '&returnUrl=' + encodeURIComponent(result.ReturnUrl);
//                    window.location.href = url;
//                });
//            }
//        };
//        //Added to select the 1st template on page loaded/angular ready. 
//        angular.element(document)
//            .ready(function () {
//                $timeout(function () { moveNextSection(-1); }, 600);
//            });
//    }]);


//})(angular.module('AutoSMART.Web.CertificateProcess'));
"use strict";

(function (module) {

    module.service('PreApprovalService', ['Config', function (Config) {
    var getPreApprovalLink = function () {
            var test = Config.PreApprovalUrl;
            return test;
        };
        return { getPreApprovalLink: getPreApprovalLink }
    }]);

})(angular.module('AutoSMART.Web.Search'));
"use strict";

(function (module) {

    module.controller('CalculatorCtrl', ['Config', '$scope', 'InfoService', '$sce', '$uibModalInstance', function (Config, $scope, InfoService, $sce, $uibModalInstance) {
    $scope.CalcData = {};
    $scope.CalcData.MonthlyPayment = null;
    $scope.CalcData.LoanTerms = [];
    $scope.CalcData.Rate = Config.Rate;
    $scope.CalcData.Term = Config.Term;
    $scope.CalcData.DownPayment = 2000;
    $scope.CalcData.TradeInValue = 0;
    $scope.CalcData.AmountOwed = 0;
    $scope.CalcData.PurchasePrice = ($scope.vehicleInfo.Price || $scope.vehicleInfo.BaseMSRP || 0);   

    InfoService.RetrieveLoanTerms().then(function(data) {
        angular.forEach(data, function(value) {
            $scope.CalcData.LoanTerms.push(value.Months);
        }); 
    });

    $scope.cancelModal = function () {
        $uibModalInstance.dismiss('cancel');
    };

    $scope.calculatePayment = function () {
        var totalLoanAmount = $scope.calculateTotalLoanAmount();
        var a = (1 + $scope.CalcData.Rate / 1200);
        var x = Math.pow(a, $scope.CalcData.Term);
        x = 1 / x;
        x = 1 - x;
        $scope.CalcData.MonthlyPayment = (totalLoanAmount) * ($scope.CalcData.Rate / 1200) / x;
    }

    $scope.calculateTotalLoanAmount = function () {
        var downPayment = $scope.CalcData.DownPayment ? $scope.CalcData.DownPayment : 0;
        var tradeIn = $scope.CalcData.TradeInValue ? $scope.CalcData.TradeInValue : 0;
        var amountOwed = $scope.CalcData.AmountOwed ? $scope.CalcData.AmountOwed : 0;
        return $scope.CalcData.PurchasePrice - downPayment - tradeIn + amountOwed;
    }

    $scope.isNumber = function(value) {
        return angular.isNumber(value);
    }

    $scope.changeTerm = function(value) {
        $scope.CalcData.Term = value;
    }

    $scope.hasRequiredValidValues = function() {
        return $scope.isNumber($scope.CalcData.Rate) && $scope.CalcData.Rate > 0 && $scope.isNumber($scope.CalcData.PurchasePrice) && $scope.CalcData.PurchasePrice > 0 && $scope.isNumber($scope.CalcData.Term);
    }

    $scope.validNumerical = function(value) {
        return $scope.isNumber(value) && value > 0;
    }
}]);
})(angular.module('AutoSMART.Web.CertificateProcess'));

"use strict";

angular.module('AutoSMART.Web.CertificateProcess')
    .directive('currency', ['$filter', '$locale', function ($filter, $locale) {
        return {
            require: 'ngModel',
            scope: true,
            link: function (scope, element, attrs, ngModel) {
                var max = parseFloat(attrs.currencyMax || 5000000);
                var min = parseFloat(attrs.currencyMin || 0);
                var decimalPlaces = 0;
                var decimalRegex = RegExp("\\d|\\-|\\" + $locale.NUMBER_FORMATS.DECIMAL_SEP, 'g');
                var clearRegex = RegExp("\\-{0,1}((\\" + $locale.NUMBER_FORMATS.DECIMAL_SEP + ")|([0-9]{1,}\\" + $locale.NUMBER_FORMATS.DECIMAL_SEP + "?))&?[0-9]{0," + decimalPlaces + "}", 'g');

                var disabled;
                var required;

                scope.$watch(function() { return (angular.isDefined(attrs.ngDisabled) && scope.$eval(attrs.ngDisabled) || false); }, function(disabledAttr) {
                    disabled = disabledAttr;
                });

                scope.$watch(function () { return (angular.isDefined(attrs.ngRequired) && scope.$eval(attrs.ngRequired) || false); }, function (requiredAttr) {
                    required = requiredAttr;
                });

                function cleanDecimal(value) {
                    value = String(value);
                    var dSeparator = $locale.NUMBER_FORMATS.DECIMAL_SEP;
                    var cleared = null;

                    var neg_dummy = $filter('currency')("-1", $locale.NUMBER_FORMATS.CURRENCY_SYM, decimalPlaces);
                    var neg_idx = neg_dummy.indexOf("1");
                    var neg_str = neg_dummy.substring(0, neg_idx);
                    value = value.replace(neg_str, "-");

                    if (RegExp("^-[\\s]*$", 'g').test(value)) 
                        value = "-0";
                    
                    if (decimalRegex.test(value)) {
                        cleared = value.match(decimalRegex).join("").match(clearRegex);
                        cleared = cleared ? cleared[0].replace(dSeparator, ".") : null;
                    }

                    if (cleared === "." || cleared === "-.")
                        cleared = ".0";

                    return cleared;
                };

                function reformatViewValue() {
                    var viewValue = currencyFormat(ngModel.$$rawModelValue);

                    ngModel.$setViewValue(viewValue);
                    ngModel.$render();
                };

                function currencyFormat(value) {
                    return $filter('currency')(value, $locale.NUMBER_FORMATS.CURRENCY_SYM, decimalPlaces);
                };

                ngModel.$parsers.push(function (viewValue) {
                    var cVal = cleanDecimal(viewValue);

                    if (viewValue !== cVal && viewValue !== currencyFormat(cVal)) {
                        ngModel.$setViewValue(cVal);
                        ngModel.$render();
                    }

                    return parseFloat(cVal);
                });

                ngModel.$formatters.unshift(currencyFormat);

                ngModel.$validators.currency = function (cVal) {
                    return (disabled || (!required && isNaN(cVal))) || (cVal <= max && cVal >= min);
                };

                element.on("blur", function () {
                    ngModel.$commitViewValue();
                    reformatViewValue();
                });

            }
        };
    }]);
"use strict";

angular.module('AutoSMART.Web.CertificateProcess')
    .directive('currencyAutomatically', ['$filter', '$locale', function ($filter, $locale) {
        return {
            require: 'ngModel',
            scope: true,
            link: function (scope, element, attrs, ngModel) {
                var max = parseFloat(attrs.currencyMax || 9999999999);
                var min = parseFloat(attrs.currencyMin || 0);
                var decimalPlaces = 0;
                var decimalRegex = RegExp("\\d|\\-|\\" + $locale.NUMBER_FORMATS.DECIMAL_SEP, 'g');
                var clearRegex = RegExp("\\-{0,1}((\\" + $locale.NUMBER_FORMATS.DECIMAL_SEP + ")|([0-9]{1,}\\" + $locale.NUMBER_FORMATS.DECIMAL_SEP + "?))&?[0-9]{0," + decimalPlaces + "}", 'g');

                var disabled;
                var required;

                scope.$watch(function() { return (angular.isDefined(attrs.ngDisabled) && scope.$eval(attrs.ngDisabled) || false); }, function(disabledAttr) {
                    disabled = disabledAttr;
                });

                scope.$watch(function () { return (angular.isDefined(attrs.ngRequired) && scope.$eval(attrs.ngRequired) || false); }, function (requiredAttr) {
                    required = requiredAttr;
                });

                function cleanDecimal(value) {
                    value = String(value);
                    var dSeparator = $locale.NUMBER_FORMATS.DECIMAL_SEP;
                    var cleared = null;

                    var neg_dummy = $filter('currency')("-1", $locale.NUMBER_FORMATS.CURRENCY_SYM, decimalPlaces);
                    var neg_idx = neg_dummy.indexOf("1");
                    var neg_str = neg_dummy.substring(0, neg_idx);
                    value = value.replace(neg_str, "-");

                    if (RegExp("^-[\\s]*$", 'g').test(value)) 
                        value = "-0";
                    
                    if (decimalRegex.test(value)) {
                        cleared = value.match(decimalRegex).join("").match(clearRegex);
                        cleared = cleared ? cleared[0].replace(dSeparator, ".") : null;
                    }

                    if (cleared === "." || cleared === "-.")
                        cleared = ".0";

                    return cleared;
                };

                function reformatViewValue() {
                    var viewValue = currencyFormat(ngModel.$$rawModelValue);

                    ngModel.$setViewValue(viewValue);
                    ngModel.$render();
                };

                function currencyFormat(value) {
                    return $filter('currency')(value, $locale.NUMBER_FORMATS.CURRENCY_SYM, decimalPlaces);
                };

                ngModel.$parsers.push(function (viewValue) {
                    var cVal = cleanDecimal(viewValue);

                    if (viewValue !== cVal && viewValue !== currencyFormat(cVal)) {
                        ngModel.$setViewValue(cVal);
                        ngModel.$render();
                    }

                    return parseFloat(cVal);
                });

                ngModel.$formatters.unshift(currencyFormat);

                ngModel.$validators.currency = function (cVal) {
                    return (disabled || (!required && isNaN(cVal))) || (cVal <= max && cVal >= min);
                };

                element.on("keyup", function () {
                    ngModel.$commitViewValue();
                    reformatViewValue();
                });

            }
        };
    }]);
"use strict";

angular.module('AutoSMART.Web.CertificateProcess')
    .directive('requestQuoteForm', ['$http', 'Config', function ($http, Config) {
        return {
            restrict: 'A',
            link: function ($scope, element) {              
                $http({
                    url: '/Certificate/GetRequestQuote',
                    method: 'GET' ,
                    params: {
                        year: $scope.vehicleInfo.Year,
                        make: $scope.vehicleInfo.MakeName,
                        model: $scope.vehicleInfo.ModelName
                    }
                }).then(function (result) {                    
                    element.html(result.data)
                        .append('<link href="../../..' + Config.BaseDirectory  + '/Content/style/JQueryUI/jquery-ui-1.9.2.custom.css" rel="stylesheet" />')
                       
                }, function (errorResult) {
                    var error = errorResult;
                });
            }
        };
    }]);
"use strict";

angular.module('AutoSMART.Web.CertificateProcess')
    .directive('ngIncludeNoCache', ['$templateCache', function ($templateCache) {
            var directive = {
                restrict: 'A',
                scope: false,
                link: function (scope, element, attributes) {
                    scope.$parent.$watch(attributes.ngInclude, function (newValue, oldValue) {
                        $templateCache.remove(oldValue);
                    });
                }
            };
            return directive;
        }
]);


'use strict';

(function (module) {
    module.service('DealerSearchFilterService', ['$http', '$q', '$location', 'Config', 'VehicleMakesService', function ($http, $q, $location, Config, VehicleMakesService) {

        var ctrl = this;
        var mainUrl = window.location.protocol + '//' + window.location.host + '/';
        //range acceptable for filter bars
        var filterLimits = {

            MinDistanceRange: 5,
            MaxDistanceRange: 255,
            DistanceIncrement: 5,
            MaxDistanceAcceptable: 250
        };

        var selectedFilters = {
            filters: {
                Makes: [],
                Distance: Config.DefaultSearchDistance,
                ZipCode: "",
                ASXOnly: false,
                ItemType: "Car",
                IsSpecialty: false,  // Used for specialty only
                MakeModel: [],
                ShowSpecficInventoryCount: false,
                MaxYear: 0,
                OnlyPreferredDealers: false
            },
            currentPage: 0
        };

        function newfilterOptions() {
            return {
                VisibleDealers: 50,
                VisibleMoreDealers: 50,
                MakeModel: [],
                Makes: [],
                VehicleType: [],
                Distance: 0,
                ZipCode: "",
                OnlyPreferredDealers: false
            };
        }

        var setupSelectedFilters = function () {
            var search = $location.search();
            var newObject = {};
            angular.forEach(search, function (value, key) {
                newObject[key.toLowerCase()] = value;
            });


            angular.forEach(selectedFilters.filters, function (value, key) {
                var keyLower = key.toLowerCase();
                if (keyLower in newObject) {
                    if (keyLower == 'makemodel') {

                        if (typeof newObject[keyLower] === 'object') {
                            angular.forEach(newObject[keyLower], function (value) {

                                if (value.indexOf('|') > 0) {
                                    var makeModelArry = value.split('|');
                                    var makeModelObj = { Make: makeModelArry[0], Model: makeModelArry[1] }
                                    selectedFilters.filters['MakeModel'].push(makeModelObj);
                                }
                            });
                        }

                        else {

                            if (newObject[keyLower].indexOf('|') > 0) {
                                var makeModelArry = newObject[keyLower].split('|');
                                var makeModelObj = { Make: makeModelArry[0], Model: makeModelArry[1] }
                                selectedFilters.filters['MakeModel'].push(makeModelObj);
                            }
                        }
                    }
                    else if (typeof selectedFilters.filters[key] === 'string') {
                        selectedFilters.filters[key] = newObject[keyLower];
                    } else if (typeof selectedFilters.filters[key] === 'boolean') {
                        selectedFilters.filters[key] = newObject[keyLower];
                    } else if (typeof selectedFilters.filters[key] === 'object') {
                        if (typeof newObject[keyLower] === 'object') {
                            angular.forEach(newObject[keyLower], function (value) {

                                selectedFilters.filters[key].push(value);
                            });
                        }

                        else
                            selectedFilters.filters[key].push(newObject[keyLower]);
                    } else if (typeof selectedFilters.filters[key] === 'number') {
                        selectedFilters.filters[key] = newObject[keyLower];
                    }
                }

            });

            selectedFilters.filters.ZipCode = Config.ZipCode;
            if (selectedFilters.filters.OnlyPreferredDealers) {
                selectedFilters.filters.Distance = 255;
            }

        }

        var getVehicleTypes = function () {
            //  if (selectedFilters.filters.IsSpecialty) {
            filterOptions.VehicleType = [];
            getSpecialtyVehicles(Config.ClientWebSiteID).then(function (data) {
                if (data.data) {
                    data = data.data;
                }
                var vehicle = { 'Key': 'Auto Dealers', 'SearchValue': 'Car' };
                filterOptions.VehicleType.push(vehicle);

                angular.forEach(data,
                    function (vehicleType) {
                        var vehicle = { 'Key': vehicleType.Key, 'SearchValue': vehicleType.SearchValue };
                        filterOptions.VehicleType.push(vehicle);

                    });
            });
        }


        function getSpecialtyVehicles(clientWebsiteID) {

            var deferred = $q.defer();
            var spe = JSON.parse(Config.SpecialtyVehicleTypes.replace(/&quot;/g, '"'));

            deferred.resolve(spe);
            return deferred.promise;
        }



        var filterOptions = newfilterOptions();

        var IsDataLoaded = false;


        var init = function () {
            if (IsDataLoaded) {
                return $q.resolve();
            }

            IsDataLoaded = true;

            setupSelectedFilters();

            return $q.all([
                getVehicleTypes(),
                VehicleMakesService.GetInventoryMakes()
                    .then(function (data) {
                        if (data.data) {
                            data = data.data;
                        }
                        angular.forEach(data,
                            function (make) {
                                filterOptions.Makes.push(make);
                            });
                        insertIndependentDealersOnly();
                    }),
            ]).then(function () {

            });
        };

        function insertIndependentDealersOnly() {
            var hasIndependentDealer = false;
            var test = filterOptions.Makes;
            angular.forEach(filterOptions.Makes, function (obj) {
                if (obj.Id == 3376 && obj.Make == "Independent Dealers Only") {
                    hasIndependentDealer = true;
                }
            });

            if (!hasIndependentDealer) {
                filterOptions.Makes.push({
                    Id: 3376,
                    Make: "Independent Dealers Only"
                });
            }
        }
        return {

            filterOptions: filterOptions,
            selectedFilters: selectedFilters,
            Init: init,
            filterLimits: filterLimits
        }
    }]);

})(angular.module('AutoSMART.Web.Dealer'));
'use strict';
(function (module) {
    module.service('DealerSearchService', [
        '$http', '$q', 'DealerSearchFilterService', 'Config', 'ZipCodeService',
        function ($http, $q, DealerSearchFilterService, Config, ZipCodeService) {
            var allDealers = {
                SearchResults: [],
                SearchTotalCount: 0
            };

            var dealerInfo = {
                SearchResults: [],
                SearchTotalCount: 0
            };

            var topCount = 250;            

            function newSearchObj() {
                var newGuid = function () {
                    return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
                        var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);
                        return v.toString(16);
                    });
                };
                return {
                    IsLoadMoreSearch: false,
                    ItemType: "Car",
                    ItemTypes: [],
                    Condition: null,
                    ClientWebsiteId: Config.ClientWebSiteID,
                    SearchString: null,
                    SearchSellersByMakeId: null,
                    Latitude: ZipCodeService.Location.Latitude || Config.CurrentLatitude,
                    Longitude: ZipCodeService.Location.Longitude || Config.CurrentLongitude,
                    DistanceRadius: Config.DefaultSearchDistance,
                    DistanceUnit: 0,
                    TopCount: topCount,
                    SortBy: 1,
                    UserGuid: newGuid(),
                    PageNumber: -1,
                    IsUserSearchTerm: false,
                    DoNotLogSearch: false,
                    LanguageId: 2,
                    TransactionGuid: newGuid(),
                    MakeModels: [],
                    DealerClientCode: null,
                    ShowSpecficInventoryCount: null,
                    SourcePlatform: 1,
                    SourceSection: 1,
                    ZipCode: "",
                    SearchRequestType: 1,
                    ExecutionTimeSearchQuery: null,
                    ExecutionTimeWholeSearch: null,
                    YearMax: null,
                    OnlyPreferredDealers: false,                    
                    ComesFromDealerSRP: false
                };
            }
            var getDealerInfo = function (dealerClientCode) {

                var searchObj = getCurrentFilterOptions();
                searchObj.DealerClientCode = dealerClientCode;
                searchObj.DistanceRadius = 251;
                var json = JSON.stringify(searchObj);
                var deferred = $q.defer();
                var link = Config.BaseDirectory + "/SearchService/PostDealerSearch";
                var promise = $http({
                    method: 'POST',
                    url: link,
                    headers: {
                        'Content-Type': 'application/json',
                        'Accept': '*/*',
                        //'Accept-Encoding': 'gzip, deflate',
                        'Accept-Language': 'en-US,en;q=0.8'
                    },
                    data: json,
                }).then(function (data) {
                    if (data.data) {
                        data = data.data;
                    }
                    dealerInfo.SearchResults = data.Owners;
                    dealerInfo.SearchTotalCount = data.SearchTotalCount;
                    dealerInfo.Loading = false;
                    deferred.resolve(data);
                }).catch(function (data) {
                    dealerInfo.Loading = false;
                    deferred.reject(data);
                });
                return deferred.promise;
            }
            var getCurrentFilterOptions = function () {

                var searchObj = newSearchObj();
                searchObj.OnlyPreferredDealers = DealerSearchFilterService.selectedFilters.filters.OnlyPreferredDealers;
                searchObj.ZipCode = DealerSearchFilterService.selectedFilters.filters.ZipCode;
                searchObj.PageNumber = DealerSearchFilterService.selectedFilters.currentPage;
                searchObj.SortBy = DealerSearchFilterService.selectedFilters.filters.SortBy;
                searchObj.OnlyAutoPremierDealers = DealerSearchFilterService.selectedFilters.filters.ASXOnly;
                searchObj.ShowSpecficInventoryCount = DealerSearchFilterService.selectedFilters.filters.ShowSpecficInventoryCount;
                if (DealerSearchFilterService.selectedFilters.filters.MaxYear && DealerSearchFilterService.selectedFilters.filters.MaxYear != 0) {
                    searchObj.YearMax = DealerSearchFilterService.selectedFilters.filters.MaxYear;
                }
                if (DealerSearchFilterService.selectedFilters.filters.Distance && DealerSearchFilterService.selectedFilters.filters.Distance !== 0) {
                    searchObj.DistanceRadius = DealerSearchFilterService.selectedFilters.filters.Distance;
                }
                searchObj.ItemType = DealerSearchFilterService.selectedFilters.filters.ItemType;
                DealerSearchFilterService.selectedFilters.filters["IsSpecialty"] = DealerSearchFilterService.selectedFilters.filters.ItemType != "Car";
                if (DealerSearchFilterService.selectedFilters.filters.IsSpecialty == false) {
                    angular.forEach(DealerSearchFilterService.selectedFilters.filters.MakeModel, function (makeModel) {
                        var obj = makeModel;
                        if (!obj.MakeId) {
                            obj.MakeId = GetMakeId(makeModel.Make);
                        }
                    });
                    searchObj.MakeModels = DealerSearchFilterService.selectedFilters.filters.MakeModel;
                    angular.forEach(DealerSearchFilterService.selectedFilters.filters.Makes, function (make) {
                        var index = -1;
                        for (var i = 0, len = searchObj.MakeModels.length; i < len; i++) {
                            if (searchObj.MakeModels[i].Make === make) {
                                index = i;
                                break;
                            }
                        }
                        if (index == -1) {
                            var makeObj = {
                                Make: make,
                                MakeId: GetMakeId(make)
                            }
                            searchObj.MakeModels.push(makeObj);
                        }
                    });
                }

                if (searchObj.MakeModels.length > 0) {
                    searchObj.ShowSpecficInventoryCount = true;
                }

                return searchObj;
            }
            function GetMakeId(make)
            {
                var makeId = 0;
                angular.forEach(DealerSearchFilterService.filterOptions.Makes, function (makeObj) {
                    if (make === makeObj.Make) {
                        makeId = makeObj.Id;

                    }
                });
                return makeId;
            }
            var retrieveDealers = function (isDealerSRP) {
                DealerSearchFilterService.selectedFilters.currentPage = 0;
                DealerSearchFilterService.filterOptions.VisibleDealers = 250;
                var searchObj = getCurrentFilterOptions();

               // amplitude.getInstance().logEvent('Dealer Search', { 'makeModel': searchObj.MakeModels });
                searchObj.ComesFromDealerSRP = isDealerSRP === true ? true : false;
                var json = JSON.stringify(searchObj);

                var deferred = $q.defer();
                var link = Config.BaseDirectory + "/SearchService/PostDealerSearch";
                var promise = $http({
                    method: 'POST',
                    url: link,
                    headers: {
                        'Content-Type': 'application/json',
                        'Accept': '*/*',
                        //'Accept-Encoding': 'gzip, deflate',
                        'Accept-Language': 'en-US,en;q=0.8'
                    },
                    data: json,
                }).then(function (data) {
                    if (data.data) {
                        data = data.data;
                    }
                    allDealers.SearchResults = data.Owners;
                    allDealers.SearchTotalCount = data.SearchTotalCount;
                    allDealers.Loading = false;

                    deferred.resolve(data);
                }).catch(function (data) {
                    allDealers.Loading = false;
                    deferred.reject(data);
                });
                return deferred.promise;
            };

            return {
                RetrieveDealers: retrieveDealers,
                allDealers: allDealers,
                GetDealerInfo: getDealerInfo,
                DealerInfo: dealerInfo //added dealerInfo so the object of allDealers does not get over written
            }
        }
    ]);
})(angular.module('AutoSMART.Web.Search'));
"use strict";
angular
    .module('AutoSMART.Web.MemberCertificate', ['ui.router', 'ui.bootstrap', 'AutoSMART.Web.Common', 'ngAria']);
    
"use strict";

(function (module) {

  module.component('membercertificatecomponent', {

    controller: ["$scope", "$uibModal", "Config", "$location", "DealerService", "$sce", "VehiclePricingService", "HidePriceDifferenceService", "QuickQuoteService", "VehicleTradeInService", "AmplitudeService",

      function ($scope, $uibModal, Config, $location, DealerService, $sce, VehiclePricingService, HidePriceDifferenceService, QuickQuoteService, VehicleTradeInService, AmplitudeService) {
        var ctrl = this;
        ctrl.data = {};
        $scope.MemberCertificate = {};
        $scope.ClientLogo = "";
        $scope.PmtClientLogo = "";
        $scope.CreditUnionName = "";
        $scope.Dealers = new Array();
        $scope.IsNewVehicle = true;
        $scope.KBBUrl = "";
        $scope.PrimaryColorAlternative = Config.PrimaryColorAlternative;
        $scope.BarcodeImageUrl = "/Content/images/barcode_gen.png";
        $scope.HasAdditionalVehicleInfo = false;
        $scope.DefaultCallCenterNumber = Config.DefaultCallCenterNumber;
        ctrl.ImageSize = 3;
        $scope.IsInventoryVehicle = true;
        $scope.dealerName = "";

        var formatPhone = function (phoneNumber) {
          if (phoneNumber === null) {
            return "";
          }

          return phoneNumber.replace(/(\d{3})(\d{3})(\d{4})/, "$1-$2-$3");
        };

        var getEstimatedStyle = function (styleId) {
          switch (styleId) {
            case 1:
              return 'great-price-price';
            case 2:
              return 'fair-price-price';
            case 3:
              return 'overpriced-price';
          }
          return '';
        };

        var setupMemberCertificate = function (data) {
          var userInfo = data[0];
          VehicleTradeInService.Init();
          $scope.TradeIn = VehicleTradeInService.Data.VehicleTradeInDetails.length > 0
            ? VehicleTradeInService.Data.VehicleTradeInDetails[0]
            : null;

          $scope.RepName = userInfo.RepName;
          $scope.RepEmail = userInfo.RepEmail;
          $scope.RepPhone = formatPhone(userInfo.RepPhone);
          $scope.ApplicationId = userInfo.AppstagingApplicationId;

          $scope.ShieldColor = Config.SecondaryColor;
          $scope.hidePriceDifference = false;
          $scope.IsNewVehicle = data[0].NewVehicle;
          $scope.IsInventoryVehicle = (data[0].StockNumber);
          $scope.UserInfo = {
            IsQuickQuotePreApproved: Config.IsQuickQuoteEnabled && QuickQuoteService.GetQQResultCookie(),
            MemberCertificateCode: userInfo.CertificateInfo.CertificateCode,
            MemberName: userInfo.WebUserName === null ? "" : userInfo.WebUserName.toUpperCase(),
            MemberPreapproval: userInfo.WebUserPreapproved,
            MemberCertificateValidThrough: userInfo.CertificateInfo.ValidThrough,
            CreateDate: new Date(userInfo.CertificateDate.match(/\d+/)[0] * 1),
            AskingPrice: typeof userInfo.Price !== 'undefined' ? userInfo.Price : 0,
            MinApr: userInfo.CertificateInfo.APRMinimum,
            MaxApr: userInfo.CertificateInfo.APRMaximum,
            MinMonthlyPayment: userInfo.CertificateInfo.APRMinimumPrice,
            MaxMonthlyPayment: userInfo.CertificateInfo.APRMaximumPrice,
            AprDuration: userInfo.CertificateInfo.APRDuration,
            BuildTrim: userInfo.CertificateInfo.BuildTrim,
            BuildStyle: userInfo.CertificateInfo.BuildStyle,
            BuildPurchasePrice: userInfo.CertificateInfo.BuildPurchasePrice,
            BuildMSRPwOptionPrice: userInfo.CertificateInfo.BuildMSRPwOptionPrice,
            BuildPaintScheme: userInfo.CertificateInfo.BuildPaintScheme,
            BuildExteriorColor: userInfo.CertificateInfo.BuildExteriorColor,
            BuildInteriorColor: userInfo.CertificateInfo.BuildInteriorColor,
            BuildPaintSchemePrice: userInfo.CertificateInfo.BuildPaintSchemePrice,
            BuildExteriorColorPrice: userInfo.CertificateInfo.BuildExteriorColorPrice,
            BuildInteriorColorPrice: userInfo.CertificateInfo.BuildInteriorColorPrice,
            BuildMSRP: userInfo.CertificateInfo.BuildMSRP,
            CertificateOptions: userInfo.CertificateInfo.CertificateOptions,
            DealTypeID: userInfo.CertificateInfo.DealTypeID,
            EstimatedText: (userInfo.CertificateInfo.EstimatedText) ? userInfo.CertificateInfo.EstimatedText : '',
            EstimatedStyle: getEstimatedStyle(userInfo.CertificateInfo.DealTypeID),
            DistanceToDealer: userInfo.CertificateInfo.DistanceToDealer,
            IsPreferredPlus: userInfo.CertificateInfo.IsPreferredPlus,
            DealerContactExt: (userInfo.DealerContactExt) ? userInfo.DealerContactExt : '',
            DealerContactName: (userInfo.DealerContactName) ? userInfo.DealerContactName : '',
            DealerContactPhone: (userInfo.DealerContactPhone) ? formatPhone(userInfo.DealerContactPhone) : '',
            Latitude: data[0].Latitude,
            Longitude: data[0].Longitude,
            Vehicle: {
              Year: userInfo.Year,
              Make: userInfo.Make === null ? "" : userInfo.Make.toUpperCase(),
              Model: userInfo.Model === null ? "" : userInfo.Model.toUpperCase(),
              Trim: userInfo.Trim === null ? "" : userInfo.Trim.toUpperCase(),
              VIN: userInfo.VIN === null ? "N/A" : userInfo.VIN,
              Price: userInfo.Price,
              ChromeStyleId: userInfo.ChromeStyleId,
              Condition: userInfo.NewVehicle ? "New" : "Used",
              StockNumber: userInfo.StockNumber === null ? "N/A" : userInfo.StockNumber,
              ImagePath: userInfo.CertificateInfo.VehicleImageURL,
              ItemId: userInfo.ItemId,
              IsStockPhoto: userInfo.IsStockPhoto
            },
            DownPayment: userInfo.DownPayment
          }
          $scope.HasStockNumber = userInfo.StockNumber !== null;
          $scope.DisclaimerVerbiage = ctrl.HtmlTagConvert(Config.LegalMemberCertificate);

          $scope.stockPhotoMessage = 'This image is a stock photo and is not an exact representation of any vehicle offered for sale. Advertised vehicles of this model may have styling, trim levels, colors and optional equipment that differ from the stock photo.';
          angular.forEach(data, function (dealerInfo) {
            DealerService.GetRating(dealerInfo.ClientCode).then(function (rating) {
              var dealer = {
                ClientCode: dealerInfo.ClientCode,
                Name: dealerInfo.ClientName,
                Street: dealerInfo.Street,
                City: dealerInfo.City,
                State: dealerInfo.State,
                Zip: dealerInfo.Zip,
                Latitude: dealerInfo.CustomLatitude === null ? dealerInfo.Latitude : dealerInfo.CustomLatitude,
                Longitude: dealerInfo.CustomLongitude === null ? dealerInfo.Longitude : dealerInfo.CustomLongitude,
                HasDealerContact: dealerInfo.HasDealerContact,
                DealerRating: rating.Average,
                TotalLifetimeReviews: rating.TotalLifetimeReviews,
                Logo: dealerInfo.DealerLogo
              };
              $scope.dealerName = dealer.Name;
              if (dealer.HasDealerContact) {
                dealer.ContactName = dealerInfo.Contact.Name;
                dealer.ContactPhone = formatPhone(dealerInfo.Contact.Phone);
                dealer.ContactPhoneExtension = dealerInfo.Contact.Extension;
              }
              $scope.Dealers.push(dealer);
              $scope.hidePriceDifference = HidePriceDifferenceService.IsDealerFromTexas(dealer.State);

              var dealId = $scope.ApplicationId ? $scope.ApplicationId : 'NotSet';

              var eventProperties = {
                  'Vehicle Type': $scope.UserInfo.Vehicle.Condition,
                  'Make': $scope.UserInfo.Vehicle.Make,
                  'Model': $scope.UserInfo.Vehicle.Model,
                  'Dealership': $scope.dealerName ? $scope.dealerName : '--',
                  'Deal Id#': $scope.ApplicationId ? $scope.ApplicationId : '--',
                  'Quick Quote Completed?': $scope.UserInfo.IsQuickQuotePreApproved ? 'True' : 'False'
              };

                if ($scope.ApplicationId) {
                    AmplitudeService.logEventWithGroups('Certificate Generated', eventProperties, { "Deal ID": dealId });
                }
                else {
                    AmplitudeService.logEvent('Certificate Generated',
                        {
                            'Vehicle Type': $scope.UserInfo.Vehicle.Condition,
                            'Make': $scope.UserInfo.Vehicle.Make,
                            'Model': $scope.UserInfo.Vehicle.Model,
                            'Dealership': $scope.dealerName ? $scope.dealerName : '--',
                            'Deal Id#': $scope.ApplicationId ? $scope.ApplicationId : '--',
                            'Quick Quote Completed?': $scope.UserInfo.IsQuickQuotePreApproved ? 'True' : 'False'
                        });
                }
               

            });
          });
        };

        var retrieveMemberCertificateByGuid = function (memberCertificateGuid) {
            DealerService.RetrieveMemberCertificate(memberCertificateGuid).then(function (data) {
                if (data.data) {
                    data = data.data;
                }
                setupMemberCertificate(data);
          });
        };

        ctrl.displayEstimateSavings = function (estimatedText) {
          if (estimatedText && estimatedText.length > 0 && estimatedText.indexOf("Below Average") !== -1) {
            return true;
          }
          return false;
        };

        var retrieveMemberCertificateGuid = function () {
          var params = $location.search();
          if (params['membercertificateguid']) {
            var guid = params['membercertificateguid'];
            return guid;
          } else {
            return '';
          }
        };

        var getReturnUrl = function () {
          var params = $location.search();
          var returnUrl = '';
          if (!params['returnUrl']) {
            return returnUrl;
          }
          returnUrl = $location.protocol() + '://' + $location.host();

          if ($location.port()) {
            returnUrl = returnUrl + ':' + $location.port();
          }
          returnUrl = decodeURIComponent(params['returnUrl']);
          $scope.ReturnLinkText = 'Back';

          return returnUrl;
        };

        ctrl.openRating = function (displayMemberCertModal) {
          ctrl.data.ClientCode = $scope.Dealers[0].ClientCode;
          $scope.displayMemberCertModal = displayMemberCertModal;
          var ratingInstance = $uibModal.open({
            component: 'dealerratercomponent',
            scope: $scope
          });

          $scope.ratingInstance = ratingInstance;
        };

        ctrl.$onInit = function () {
          var memberCertificateGuid = retrieveMemberCertificateGuid();
          retrieveMemberCertificateByGuid(memberCertificateGuid);
          $scope.MemberCertificateReturnUrl = getReturnUrl();
          $scope.ClientLogo = Config.ClientColorlessLogoUrl;
          $scope.PmtClientLogo = Config.ClientLogo;
          $scope.ShieldColor = Config.SecondaryColor;
          $scope.CreditUnionName = Config.ClientName;
        };

        ctrl.HtmlTagConvert = function (convert) {
          var openTag = "&lt;p&gt;";
          var closeTag = "&lt;/p&gt;";
          var reOpenTag = new RegExp(openTag, 'g');
          var reCloseTag = new RegExp(closeTag, 'g');
          var convertTags = convert.replace(reOpenTag, "<p>").replace(reCloseTag, "</p>");
          return $sce.trustAsHtml(convertTags);
        };
      }],
    templateUrl: '/App/MemberCertificate/Views/MemberCertificate.html?s'
  });
})(angular.module('AutoSMART.Web.MemberCertificate'));

angular.module('AutoSMART.Web.MemberCertificate').factory('DealerService', [
    '$http', 'Config', '$q', function ($http, Config, $q) {

        var retrieveMemberCertificate = function (memberCertificateGuid) {
            var deferred = $q.defer();          

            var link = Config.BaseDirectory + '/certificateService/GetMemberCertificate';
            $http({
                    url: link,
                    method: "GET",
                    params: { guid: memberCertificateGuid }
                })
                .then(function (terms) {
                    deferred.resolve(terms.data);
                }, function (errorResult) {
                    deferred.reject(errorResult);
                });
            return deferred.promise;
        };


        var getRating = function (clientCode) {
            var deferred = $q.defer();

            var link = Config.BaseDirectory + '/certificateService/GetDealerRating';
            $http({
                url: link,
                method: "GET",
                params: { clientCode: clientCode }
            })
                .then(function (terms) {
                    deferred.resolve(terms.data);
                }, function (errorResult) {
                    deferred.reject(errorResult);
                });
            return deferred.promise;
        };

        return {
            GetRating: getRating,
            RetrieveMemberCertificate: retrieveMemberCertificate
        };
    }])
"use strict";

(function (module) {

    module.component('prequalifycomponent', {
        templateUrl: '/app/prequalify/views/PreQualify.html',
        controller: ['$scope', '$window', 'ZipCodeService', 'UserService', "QuickQuoteService", 'Config', 'GoogleAnalyticsService', 'insights', 'AmplitudeService',

            function ($scope, $window, ZipCodeService, UserService, QuickQuoteService, Config, GoogleAnalyticsService, insights, AmplitudeService) {
                var $ctrl = this;
                AmplitudeService.logEvent('Quick Quote Basics Page Loaded',
                    {
                        'Quick Quote Basics Page Loaded': 'Quick Quote Basics Page Loaded'
                    });
                $ctrl.pqFormValues = {
                    FirstName: "",
                    LastName: "",
                    HomeAddress: "",
                    ZipCode: "",
                    City: "",
                    State: "",
                    DateOfBirth: "",
                    GrossAnnualIncome: "",
                    PqTermsAndConditionsCheck: false,
                    DownPayment: 0
                };
                $ctrl.validZip = true;
                $ctrl.validDob = true;
                $ctrl.validAge = true;
                $ctrl.validFirstName = true;
                $ctrl.states = [];
                $ctrl.termsURL = "/Content/Terms";
                $ctrl.creditUnionName = Config.ClientName;
                getSessionSetting();
                ZipCodeService.Init().then(function () {
                    var zip = ZipCodeService.Location.CurrentZipCode;
                    $ctrl.pqFormValues.ZipCode = zip;
                    $ctrl.setCityState(zip);
                });

                $ctrl.goPage = function (page) {
                    var url = '';
                    switch (page) {
                        case 'QQResult':
                            url = '/QQResult';
                            break;
                        default:
                    }
                    if (url) {
                        $window.location.href = url;
                    }
                };

                $(function () {
                    $('#pqFirstName, #pqLastName, #pqCity').keypress(function (e) {
                        let allow_char = [45, 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, 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, 32, 39, 8];
                        if (allow_char.indexOf(e.which) === -1) {
                            return false;
                        }
                    });
                });

                $(function () {
                    $('#pqHomeAddress').keypress(function (e) {
                        let allow_char = [45, 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, 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, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 32, 39, 8];
                        if (allow_char.indexOf(e.which) === -1) {
                            return false;
                        }
                    });
                });

                $(function () {
                    $('#pqZipCode').keypress(function (e) {
                        let allow_char = [48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 8];
                        if (allow_char.indexOf(e.which) === -1) {
                            return false;
                        }
                    });
                });

                $(function () {
                    $('#pqGrossAnnualIncome').keypress(function (e) {
                        return (e.charCode == 8 || e.charCode == 0 || e.charCode == 13) ? null : e.charCode >= 48 && e.charCode <= 57;
                    });
                });

                $ctrl.validateZip = function () {
                    var zip = $ctrl.pqFormValues.ZipCode;
                    if (zip && zip.length === 5) {
                        $ctrl.setCityState(zip);
                    }
                    else {
                        $ctrl.validZip = false;
                        $('#pqZipCode').addClass("invalid");
                        $('#zip-error').removeClass("hidden");
                    }
                };

                $ctrl.setCityState = function (zip) {
                    $.ajax({
                        type: "GET",
                        url: "/PreQualify/GetCityAndStateFromZipCode/?zip=" + zip,
                        dataType: 'json',
                        cache: false,
                        success: function (result) {
                            if (result) {
                                $ctrl.pqFormValues.City = result['City'];
                                $ctrl.pqFormValues.State = result['LocationName'];
                                $ctrl.validZip = true;
                                $('#pqZipCode').removeClass("invalid");
                                $('#zip-error').addClass("hidden");
                            } else {
                                $ctrl.validZip = false;
                                $('#pqZipCode').addClass("invalid");
                                $('#zip-error').removeClass("hidden");
                            }
                            $scope.$digest();
                        }
                    });
                };

                $ctrl.$onInit = function () {
                    if (!Config.IsQuickQuoteEnabled) {
                        $window.location.href = '/';
                    }
                    ZipCodeService.GetStates().then(function(states){
                        angular.forEach(states, function (s) {
                            var stateObj = { 'name': s.Name };
                            $ctrl.states.push(stateObj);
                        });
                    });
                };

                $ctrl.validateDateOfBirth = function () {
                    $ctrl.validAge = true;
                    $ctrl.validDob = true;
                    var strDob = $ctrl.pqFormValues.DateOfBirth;
                    if (!strDob || strDob.length < 8) {
                        $ctrl.validDob = false;
                        $('#pqDateOfBirth').addClass("invalid");
                        return false;
                    }
                    var month = strDob.substring(0, 2);
                    var day = strDob.substring(2, 4);
                    var year = strDob.substring(4, 8);
                    if (year < 1800 || year > 3000 || month === 0 || month > 12) {
                        $ctrl.validDob = false;
                        $('#pqDateOfBirth').addClass("invalid");
                        return false;
                    }

                    var monthLength = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
                    // Adjust for leap years
                    if (year % 400 === 0 || (year % 100 !== 0 && year % 4 === 0)) {
                        monthLength[1] = 29;
                    }
                    // Check the range of the day
                    if (day < 1 || day > monthLength[month - 1]) {
                        $ctrl.validDob = false;
                        $('#pqDateOfBirth').addClass("invalid");
                        return false;
                    }

                    var dob = new Date(year + "-" + month + "-" + day);
                    var ageDifMs = Date.now() - dob.getTime();
                    var ageDate = new Date(ageDifMs); // miliseconds from epoch
                    var age = Math.abs(ageDate.getUTCFullYear() - 1970);

                    if (dob > Date.now()) {
                        $ctrl.validDob = false;
                        $('#pqDateOfBirth').addClass("invalid");
                        return false;
                    }

                    if (age < 18) {
                        $ctrl.validAge = false;
                        $('#pqDateOfBirth').addClass("invalid");
                        return false;
                    }
                    $('#pqDateOfBirth').removeClass("invalid");
                    return true;
                };

                $ctrl.qqSubmitButtonClicked = function () {
                    AmplitudeService.logEvent('Quick Quote Application Submitted',
                    {
                        'Quick Quote Application Submitted': 'Quick Quote Application Submitted'
                    });
                    insights.logEvent("Quick Quote Continue Btn Clicked", { 'ApplicantName': $ctrl.pqFormValues.FirstName + " " + $ctrl.pqFormValues.LastName });
                    $ctrl.PrequalifyContinueBtn = GoogleAnalyticsService.PrequalifyContinueBtn;
                    $ctrl.PrequalifyContinueBtn();
                    ZipCodeService.SaveZipCode($ctrl.pqFormValues.ZipCode);
                    QuickQuoteService.SavePreQualFormData($ctrl.pqFormValues);
                    $ctrl.goPage('QQResult');
                    UserService.SaveUserSettings($ctrl.pqFormValues.FirstName, $ctrl.pqFormValues.LastName, $ctrl.pqFormValues.PqTermsAndConditionsCheck);
                };

                function getSessionSetting() {
                    var result = sessionStorage.getItem("qqSessionResult");
                    if (typeof result != 'undefined' && result != null) {
                        var selectedOptions = JSON.parse(result);
                        if (selectedOptions !== null && selectedOptions !== undefined) {
                            $ctrl.pqFormValues = {
                                FirstName: selectedOptions.FirstName,
                                LastName: selectedOptions.LastName,
                                HomeAddress: selectedOptions.HomeAddress,
                                ZipCode: selectedOptions.ZipCode,
                                City: selectedOptions.City,
                                State: selectedOptions.State,
                                GrossAnnualIncome: selectedOptions.GrossAnnualIncome
                            };
                        }
                    }
                };
            }
        ]
    });

})(angular.module('AutoSMART.Web.PreQualify'));

'use strict';

(function (module) {

    module.component('qqresultcomponent', {

        controller: ['Config', 'ZipCodeService', 'QuickQuoteService', 'insights', 'AmplitudeService', 'GoogleAnalyticsService','$window',
            function (Config, ZipCodeService, QuickQuoteService, insights, AmplitudeService, GoogleAnalyticsService, $window) {
                var ctrl = this;

                ctrl.editing = false;
                ctrl.$onInit = function () {
                    var correlationId = insights.randomString(5);
                    if (!Config.IsQuickQuoteEnabled) {
                        $window.location.href = '/';
                    }
                    insights.logEvent("Quick Quote Service page load start", { 'tenentId': Config.ClientCode, "correlationId": correlationId });           
                    document.title = 'AutoSMART - Congratulations';
                    ctrl.loading = true;

                    ZipCodeService.Init().then(function () {
                        ctrl.Location = ZipCodeService.Location;
                    });
                    QuickQuoteService.SubmitQuickQuoteRequest().then(function (success) {
                        insights.logEvent("Quick Quote Request Successfull ", { 'tenentId': Config.ClientCode, "correlationId": correlationId });  
                        ctrl.rates = QuickQuoteService.GetRates();
                        if (ctrl.rates) {
                            ctrl.loading = true;
                            var defaultNewRate = ctrl.rates.new[0] ? ctrl.rates.new[0] : null;
                            var defaultUsedRate = ctrl.rates.used[0] ? ctrl.rates.used[0] : null;
                            if (defaultUsedRate){
                                for (var i = 0; i < ctrl.rates.used.length; i++) {
                                    if (ctrl.rates.used[i].approvedTerm == 60) {
                                        defaultUsedRate = ctrl.rates.used[i];
                                    }
                                }
                                ctrl.resultmaxloanamount = defaultUsedRate.loanAmount;
                                ctrl.newSelected = false;
                                ctrl.selectedRateType = ctrl.rates.used;
                                ctrl.selectedUsedRate = defaultUsedRate;
                                ctrl.rateSelected = defaultUsedRate;
                            }
                            if (defaultNewRate) {
                                for (i = 0; i < ctrl.rates.new.length; i++) {
                                    if (ctrl.rates.new[i].approvedTerm == 60) {
                                        defaultNewRate = ctrl.rates.new[i];
                                    }
                                }
                                ctrl.resultmaxloanamount = defaultNewRate.loanAmount;
                                ctrl.newSelected = true;
                                ctrl.selectedRateType = ctrl.rates.new;
                                ctrl.selectedNewRate = defaultNewRate;
                                ctrl.rateSelected = defaultNewRate
                            }   
                            ctrl.selectedmaxloanamount = ctrl.resultmaxloanamount.toString();
                            ctrl.formatCurrency();
                            ctrl.loading = false;

                            if (success?.borrower?.birthDate) {
                                const birthDate = new Date(success.borrower.birthDate);
                                success.borrower.birthDate = birthDate.getFullYear();
                            }
                            if (success?.borrower?.firstName)
                                delete success.borrower.firstName;
                            if (success?.borrower?.lastName)
                                delete success.borrower.lastName;
                            if (success?.borrower?.residence?.streetAddress)
                                delete success.borrower.residence.streetAddress; 

                            AmplitudeService.logEvent('Quick Quote Loan Rates Viewed', {
                                'credit union': Config.ClientName,
                                'amount pre-approved': ctrl.selectedmaxloanamount,
                                'borrower': success.borrower,
                                'lenderResponses': success.lenderResponses[0]
                            });
                            insights.logEvent("Quick Quote Service page load finish", { 'tenentId': Config.ClientCode, "correlationId": correlationId });                 
                        }
                        else {

                            if (success?.responseMessage) {
                                var result = sessionStorage.getItem("qqSessionResult");
                                if (typeof result != 'undefined' && result != null) {
                                    var selectedOptions = JSON.parse(result);
                                    if (selectedOptions !== null || selectedOptions !== undefined) {
                                        selectedOptions.DateOfBirth.replace(/(\d\d)(\d\d)(\d\d\d\d)/, function (m, g1, g2, g3) {
                                               return selectedOptions.DateOfBirth = g3;
                                        });
                                        var declineReason = QuickQuoteService.GetQQRejectReason();
                                        AmplitudeService.logEvent('No personalized Rates for Quick Quote applicant',
                                            {
                                                'Credit Union': Config.ClientName,
                                                'City': selectedOptions.City,
                                                'Gross Annual Income': '$' + selectedOptions.GrossAnnualIncome,
                                                'State': selectedOptions.State,
                                                'Year of Birth': selectedOptions.DateOfBirth,
                                                'Decline Reason': declineReason ? JSON.stringify(declineReason) : '--'
                                            });
                                    }
                                } 
                            }
                            else {
                                if (success?.borrower?.birthDate) {
                                    const birthDate = new Date(success.borrower.birthDate);
                                    success.borrower.birthDate = birthDate.getFullYear();
                                }
                                if (success?.borrower?.firstName)
                                    delete success.borrower.firstName;
                                if (success?.borrower?.lastName)
                                    delete success.borrower.lastName;
                                if (success?.borrower?.residence?.streetAddress)
                                    delete success.borrower.residence.streetAddress; 

                                AmplitudeService.logEvent('No personalized Rates for Quick Quote applicant',
                                    {
                                        'Credit Union': Config.ClientName,
                                        'borrower': success.borrower,
                                        'lenderResponses': success.lenderResponses[0]
                                    });

                            }                                                   

                            insights.logEvent("Quick Quote Service Returned No Results", { 'tenentId': Config.ClientCode, "correlationId": correlationId });                 
                            window.location.href = '/QQNoResults';
                        }
                    }, function (error) {
                            //change to error page
                            insights.logEvent("Quick Quote Service Error", { 'tenentId': Config.ClientCode, "correlationId": correlationId });                 
                            window.location.href = '/QQNoResults';
                        });                    
                };

                ctrl.goPage = function (page) {
                    var url = '';
                    var zip = ZipCodeService.Location.CurrentZipCode;
                    var strMaxLoanAmount = ctrl.selectedmaxloanamount.replace(',', '').replace('$', '');
                    var maxLoanAmount = parseInt(strMaxLoanAmount, 10) * 1.1; //add 10% buffer
                    maxLoanAmount = parseInt(maxLoanAmount, 10); // round to dollars
                    var newOrUsed = 'New';
                    if (ctrl.selectedRateType === ctrl.rates.used) {
                        newOrUsed = 'Used';
                    }

               //     console.log(ctrl.selectedNewRate);
               //     amplitude.getInstance().logEvent('Quick Quote Loan Option Selected', { 'cuCode': Config.ClientCode, 'amount preapproved': ctrl.selectedmaxloanamount, 'loan type': newOrUsed, 'rate': ctrl.selectedNewRate, 'term': 60, 'monthly payment amount': 'test amount'});
                    QuickQuoteService.SaveQQSelection(ctrl.selectedNewRate, ctrl.selectedUsedRate);
                    switch (page) {
                        case 'ShopForCars':
                            ctrl.ShopforCarsBtn = GoogleAnalyticsService.ShopforCarsBtn;
                            ctrl.ShopforCarsBtn();

                            AmplitudeService.logEvent('Quick Quote Loan Option Selected',
                                {
                                    'Credit Union': window.location.hostname,
                                    'Amount Pre-approved': '$' + ctrl.rateSelected.loanAmount.toString().replace(/(\d)(?=(\d{3})+$)/g, '$1,'),
                                    'Loan Type': ctrl.rateSelected.loanProduct + ' Loans',
                                    'Rate': ctrl.rateSelected.approvedRate + '%',
                                    'Term': ctrl.rateSelected.approvedTerm + ' Months',
                                    'Monthly Payment Amount': '$' + ctrl.rateSelected.monthlyPayment.toString().replace(/(\d)(?=(\d{3})+$)/g, '$1,') + '/mo'
                                });

                            url = '/Search/?MaxPrice=' + maxLoanAmount + '&ZipCode=' + zip + '&Condition=' + newOrUsed;
                            break;
                        default:
                    }
                    if (url) {
                        window.location.href = url;
                    }
                };

                ctrl.toggleEdit = function () {
                    ctrl.editing = !ctrl.editing;
                    if (ctrl.editing === true) {
                        setTimeout(function () {
                            $('#max-loan-amount-edit').focus();
                        }, 10);
                    }
                };
                

                ctrl.formatCurrency = function () {
                    var result = ctrl.selectedmaxloanamount ?
                        ctrl.selectedmaxloanamount.toString() : '';
                    result = result.replace(/(\d)(?=(\d{3})+$)/g, '$1,');
                    ctrl.selectedmaxloanamount = '$' + result;
                };

                ctrl.getCurrentAmount = function () {
                    return parseInt(ctrl.selectedmaxloanamount.replace(/[^0-9]/g, ''));
                };

                ctrl.loanAmountKeydown = function (event) {
                    if (!ctrl.isTextEditKey(event.key) && !ctrl.isDigitKey(event.key)) {
                        event.preventDefault();
                    } else {
                        if (event.key === 'Enter') {
                            ctrl.finishEditing();
                        }
                    }
                };

                //keyup
                ctrl.validateMaxLoanAmount = function () {
                    var amount = ctrl.getCurrentAmount();
                    if (ctrl.resultmaxloanamount < amount) {
                        ctrl.errorText = 'Maximum loan amount exceeded';
                        ctrl.error = true;
                    } else if (amount < 10000) {
                        ctrl.errorText = 'Minimum loan amount not met';
                        ctrl.error = true;
                    } else {
                        ctrl.selectedmaxloanamount = amount;
                        ctrl.error = false;
                    }
                    ctrl.selectedmaxloanamount = amount;
                    ctrl.formatCurrency();
                };

                ctrl.finishEditing = function () {
                    if (ctrl.editing) {
                        ctrl.validateMaxLoanAmount();
                        if (!ctrl.error) {
                            ctrl.editing = false;
                            ctrl.recalculateMonthlyPayments();
                        }
                    }
                };

                ctrl.recalculateMonthlyPayments = function () {
                    ctrl.rates.new.forEach(function (item) { item.loanAmount = ctrl.getCurrentAmount(); });
                    ctrl.rates.new.forEach(function (item) { item.monthlyPayment = QuickQuoteService.QQResultItemWithMonthlyPayment(item).monthlyPayment; });

                    ctrl.rates.used.forEach(function (item) { item.loanAmount = ctrl.getCurrentAmount(); });
                    ctrl.rates.used.forEach(function (item) { item.monthlyPayment = QuickQuoteService.QQResultItemWithMonthlyPayment(item).monthlyPayment; });
                };

                
                ctrl.isTextEditKey = function (key) {
                    return key === 'ArrowUp' || key === 'ArrowDown'
                        || key === 'ArrowLeft' || key === 'ArrowRight'
                        || key === 'Backspace' || key === 'Delete'
                        || key === 'Escape' || key === 'Tab' || key === 'Enter'
                        || key === 'Home' || key === 'End';
                };

                ctrl.isDigitKey = function (key) {
                    var num = parseInt(key);
                    return num >= 0 && num <= 9;
                };

                ctrl.usedclick = function () {
                    if (ctrl.newSelected) {
                        ctrl.newSelected = false;
                        ctrl.selectedRateType = ctrl.rates.used;
                        ctrl.rateSelected = ctrl.selectedUsedRate;
                    }
                };

                ctrl.newclick = function () {
                    if (!ctrl.newSelected) {
                        ctrl.newSelected = true;
                        ctrl.selectedRateType = ctrl.rates.new;
                        ctrl.rateSelected = ctrl.selectedNewRate;
                    }
                };

                ctrl.selectRate = function (selectedItem) {
                    let relativeRate;
                    if (!ctrl.rateSelected || !ctrl.isTheSameRate(selectedItem.approvedRate, selectedItem.approvedTerm, selectedItem.loanProduct, ctrl.rateSelected)) {
                        var isNewSelected = selectedItem.loanProduct === 'New Auto';
                        var loanTypeCollection = isNewSelected ? ctrl.rates.new : ctrl.rates.used;
                        ctrl.rateSelected = loanTypeCollection.filter(function (item) {
                            return ctrl.isTheSameRate(selectedItem.approvedRate, selectedItem.approvedTerm, selectedItem.loanProduct, item);
                        })[0];
                        if (isNewSelected) {
                            for (var i = 0; i < ctrl.rates.used.length; i++) {
                                if (ctrl.rates.used[i].approvedTerm == ctrl.rateSelected.approvedTerm) {
                                    relativeRate = ctrl.rates.used[i];
                                }
                            }
                            if (!relativeRate) {
                                relativeRate = defaultNewRate;
                            }
                            ctrl.selectedNewRate = ctrl.rateSelected;
                            ctrl.selectedUsedRate = relativeRate;
                        } else {
                            for (var i = 0; i < ctrl.rates.new.length; i++) {
                                if (ctrl.rates.new[i].approvedTerm == ctrl.rateSelected.approvedTerm) {
                                    relativeRate = ctrl.rates.new[i];
                                }
                            }
                            if (!relativeRate) {
                                relativeRate = defaultUsedRate;
                            }
                            ctrl.selectedNewRate = relativeRate;
                            ctrl.selectedUsedRate = ctrl.rateSelected;
                        }
                    }
                   
                };

                ctrl.isTheSameRate = function (approvedRate, approvedTerm, loanProduct, rateObject) {
                    return rateObject
                        && rateObject.approvedRate === approvedRate
                        && rateObject.approvedTerm === approvedTerm
                        && rateObject.loanProduct === loanProduct;
                };

                ctrl.creditUnionName = Config.ClientName;
            }],
        templateUrl: UrlContent('/App/QQResult/Views/Congratulations.html')
    });
})(angular.module('AutoSMART.QQResult'));

'use strict';

(function (module) {

    module.component('qqnoresultscomponent', {

        controller: ['Config', 'GoogleAnalyticsService', 'AmplitudeService', '$window', 'QuickQuoteService',
            function (Config, GoogleAnalyticsService, AmplitudeService, $window, QuickQuoteService) {
                var ctrl = this;
              
                ctrl.editing = false;
                ctrl.$onInit = function () {
                    if (!Config.IsQuickQuoteEnabled) {
                        $window.location.href = '/';
                    }
                    document.title = 'AutoSMART - Sorry';
                    ctrl.loading = false;                   
                    //var result = sessionStorage.getItem("qqSessionResult");
                    //if (typeof result != 'undefined' && result != null) {
                    //    var selectedOptions = JSON.parse(result);
                    //    if (selectedOptions !== null || selectedOptions !== undefined) {
                    //        selectedOptions.DateOfBirth.replace(/(\d\d)(\d\d)(\d\d\d\d)/, function (m, g1, g2, g3) {
                    //           // return selectedOptions.DateOfBirth = g2 + "/" + g1 + "/" + g3;
                    //            return selectedOptions.DateOfBirth =  g3;
                    //        });
                    //        var declineReason = QuickQuoteService.GetQQRejectReason();
                    //        AmplitudeService.logEvent('No personalized Rates for Quick Quote applicant',
                    //            {
                    //                'Credit Union': Config.ClientName,
                    //               // 'First Name': selectedOptions.FirstName,
                    //               // 'Last Name': selectedOptions.LastName,
                    //                'City': selectedOptions.City,
                    //                'Gross Annual Income': '$' + selectedOptions.GrossAnnualIncome,
                    //                'State': selectedOptions.State,
                    //                'Year of Birth': selectedOptions.DateOfBirth,
                    //                'Decline Reason': declineReason ? JSON.stringify(declineReason) : '--'
                    //            });
                    //    }
                    //} 
                };               

                ctrl.goPage = function (page) {
                    var url = '';
                    switch (page) {
                        case 'Continue':
                            url = '/Search';
                            ctrl.ContinueShoppingBtn = GoogleAnalyticsService.ContinueShoppingBtn;
                            ctrl.ContinueShoppingBtn();
                            break;
                        case 'Back':
                            url = '/PreQualify';
                            break;

                        default:
                    }
                    if (url) {
                        ctrl.loading = true;
                        window.location.href = url;
                    }
                };                
                             
                ctrl.creditUnionName = Config.ClientName;
                ctrl.City = Config.CEMCity;
                ctrl.Street = Config.CEMStreet.replace(/&amp;/g, "&").replace(/&lt;/g, "<").replace(/&gt;/g, ">");
                ctrl.Zip = Config.CEMZip;
                ctrl.County = Config.CEMCountry;
                ctrl.Phone = Config.CEMPhone;
                ctrl.Ext = Config.CEMExt;
                ctrl.Email = Config.CEMCuEmail;
                ctrl.State = Config.DefaultState;
            }],
     
        templateUrl: UrlContent('/App/QQNoResults/Views/QQNoResult.html')
    });
})(angular.module('AutoSMART.QQNoResults'));

