text: // https://tc39.github.io/ecma262/#sec-array.prototype.find
  //source: https://github.com/Modernizr/Modernizr/wiki/HTML5-Cross-Browser-Polyfills
  
  
  /*
  compiled by <sunils@ilimi.in> Sunil A S
  
  Polyfill for Array:
  1. find
  2. filter
  3. forEach
  4. every
  5. indexOf
  6. map
  7. lastIndexOf
  8. reduce
  9. reduceRight
  10. isArray
  
  */
  if (!Array.prototype.find) {
    Object.defineProperty(Array.prototype, 'find', {
      value: function(predicate) {
       // 1. Let O be ? ToObject(this value).
        if (this == null) {
          throw new TypeError('"this" is null or not defined');
        }
  
        var o = Object(this);
  
        // 2. Let len be ? ToLength(? Get(O, "length")).
        var len = o.length >>> 0;
  
        // 3. If IsCallable(predicate) is false, throw a TypeError exception.
        if (typeof predicate !== 'function') {
          throw new TypeError('predicate must be a function');
        }
  
        // 4. If thisArg was supplied, let T be thisArg; else let T be undefined.
        var thisArg = arguments[1];
  
        // 5. Let k be 0.
        var k = 0;
  
        // 6. Repeat, while k < len
        while (k < len) {
          // a. Let Pk be ! ToString(k).
          // b. Let kValue be ? Get(O, Pk).
          // c. Let testResult be ToBoolean(? Call(predicate, T, « kValue, k, O »)).
          // d. If testResult is true, return kValue.
          var kValue = o[k];
          if (predicate.call(thisArg, kValue, k, o)) {
            return kValue;
          }
          // e. Increase k by 1.
          k++;
        }
  
        // 7. Return undefined.
        return undefined;
      }
    });
  }
  
  if (!Array.prototype.filter) {
    Array.prototype.filter = function(fun/*, thisArg*/) {
      'use strict';
  
      if (this === void 0 || this === null) {
        throw new TypeError();
      }
  
      var t = Object(this);
      var len = t.length >>> 0;
      if (typeof fun !== 'function') {
        throw new TypeError();
      }
  
      var res = [];
      var thisArg = arguments.length >= 2 ? arguments[1] : void 0;
      for (var i = 0; i < len; i++) {
        if (i in t) {
          var val = t[i];
  
          // NOTE: Technically this should Object.defineProperty at
          //       the next index, as push can be affected by
          //       properties on Object.prototype and Array.prototype.
          //       But that method's new, and collisions should be
          //       rare, so use the more-compatible alternative.
          if (fun.call(thisArg, val, i, t)) {
            res.push(val);
          }
        }
      }
  
      return res;
    };
  }
  
  // Production steps of ECMA-262, Edition 5, 15.4.4.18
  // Reference: http://es5.github.io/#x15.4.4.18
  if (!Array.prototype.forEach) {
  
    Array.prototype.forEach = function(callback/*, thisArg*/) {
  
      var T, k;
  
      if (this == null) {
        throw new TypeError('this is null or not defined');
      }
  
      // 1. Let O be the result of calling toObject() passing the
      // |this| value as the argument.
      var O = Object(this);
  
      // 2. Let lenValue be the result of calling the Get() internal
      // method of O with the argument "length".
      // 3. Let len be toUint32(lenValue).
      var len = O.length >>> 0;
  
      // 4. If isCallable(callback) is false, throw a TypeError exception. 
      // See: http://es5.github.com/#x9.11
      if (typeof callback !== 'function') {
        throw new TypeError(callback + ' is not a function');
      }
  
      // 5. If thisArg was supplied, let T be thisArg; else let
      // T be undefined.
      if (arguments.length > 1) {
        T = arguments[1];
      }
  
      // 6. Let k be 0
      k = 0;
  
      // 7. Repeat, while k < len
      while (k < len) {
  
        var kValue;
  
        // a. Let Pk be ToString(k).
        //    This is implicit for LHS operands of the in operator
        // b. Let kPresent be the result of calling the HasProperty
        //    internal method of O with argument Pk.
        //    This step can be combined with c
        // c. If kPresent is true, then
        if (k in O) {
  
          // i. Let kValue be the result of calling the Get internal
          // method of O with argument Pk.
          kValue = O[k];
  
          // ii. Call the Call internal method of callback with T as
          // the this value and argument list containing kValue, k, and O.
          callback.call(T, kValue, k, O);
        }
        // d. Increase k by 1.
        k++;
      }
      // 8. return undefined
    };
  }
  
  
  if (!Array.prototype.every) {
    Array.prototype.every = function(callbackfn, thisArg) {
      'use strict';
      var T, k;
  
      if (this == null) {
        throw new TypeError('this is null or not defined');
      }
  
      // 1. Let O be the result of calling ToObject passing the this 
      //    value as the argument.
      var O = Object(this);
  
      // 2. Let lenValue be the result of calling the Get internal method
      //    of O with the argument "length".
      // 3. Let len be ToUint32(lenValue).
      var len = O.length >>> 0;
  
      // 4. If IsCallable(callbackfn) is false, throw a TypeError exception.
      if (typeof callbackfn !== 'function') {
        throw new TypeError();
      }
  
      // 5. If thisArg was supplied, let T be thisArg; else let T be undefined.
      if (arguments.length > 1) {
        T = thisArg;
      }
  
      // 6. Let k be 0.
      k = 0;
  
      // 7. Repeat, while k < len
      while (k < len) {
  
        var kValue;
  
        // a. Let Pk be ToString(k).
        //   This is implicit for LHS operands of the in operator
        // b. Let kPresent be the result of calling the HasProperty internal 
        //    method of O with argument Pk.
        //   This step can be combined with c
        // c. If kPresent is true, then
        if (k in O) {
  
          // i. Let kValue be the result of calling the Get internal method
          //    of O with argument Pk.
          kValue = O[k];
  
          // ii. Let testResult be the result of calling the Call internal method
          //     of callbackfn with T as the this value and argument list 
          //     containing kValue, k, and O.
          var testResult = callbackfn.call(T, kValue, k, O);
  
          // iii. If ToBoolean(testResult) is false, return false.
          if (!testResult) {
            return false;
          }
        }
        k++;
      }
      return true;
    };
  }
  
  
  // Production steps of ECMA-262, Edition 5, 15.4.4.14
  // Reference: http://es5.github.io/#x15.4.4.14
  if (!Array.prototype.indexOf) {
    Array.prototype.indexOf = function(searchElement, fromIndex) {
  
      var k;
  
      // 1. Let o be the result of calling ToObject passing
      //    the this value as the argument.
      if (this == null) {
        throw new TypeError('"this" is null or not defined');
      }
  
      var o = Object(this);
  
      // 2. Let lenValue be the result of calling the Get
      //    internal method of o with the argument "length".
      // 3. Let len be ToUint32(lenValue).
      var len = o.length >>> 0;
  
      // 4. If len is 0, return -1.
      if (len === 0) {
        return -1;
      }
  
      // 5. If argument fromIndex was passed let n be
      //    ToInteger(fromIndex); else let n be 0.
      var n = fromIndex | 0;
  
      // 6. If n >= len, return -1.
      if (n >= len) {
        return -1;
      }
  
      // 7. If n >= 0, then Let k be n.
      // 8. Else, n<0, Let k be len - abs(n).
      //    If k is less than 0, then let k be 0.
      k = Math.max(n >= 0 ? n : len - Math.abs(n), 0);
  
      // 9. Repeat, while k < len
      while (k < len) {
        // a. Let Pk be ToString(k).
        //   This is implicit for LHS operands of the in operator
        // b. Let kPresent be the result of calling the
        //    HasProperty internal method of o with argument Pk.
        //   This step can be combined with c
        // c. If kPresent is true, then
        //    i.  Let elementK be the result of calling the Get
        //        internal method of o with the argument ToString(k).
        //   ii.  Let same be the result of applying the
        //        Strict Equality Comparison Algorithm to
        //        searchElement and elementK.
        //  iii.  If same is true, return k.
        if (k in o && o[k] === searchElement) {
          return k;
        }
        k++;
      }
      return -1;
    };
  }
  
  // Production steps of ECMA-262, Edition 5, 15.4.4.19
  // Reference: http://es5.github.io/#x15.4.4.19
  if (!Array.prototype.map) {
  
    Array.prototype.map = function(callback/*, thisArg*/) {
  
      var T, A, k;
  
      if (this == null) {
        throw new TypeError('this is null or not defined');
      }
  
      // 1. Let O be the result of calling ToObject passing the |this| 
      //    value as the argument.
      var O = Object(this);
  
      // 2. Let lenValue be the result of calling the Get internal 
      //    method of O with the argument "length".
      // 3. Let len be ToUint32(lenValue).
      var len = O.length >>> 0;
  
      // 4. If IsCallable(callback) is false, throw a TypeError exception.
      // See: http://es5.github.com/#x9.11
      if (typeof callback !== 'function') {
        throw new TypeError(callback + ' is not a function');
      }
  
      // 5. If thisArg was supplied, let T be thisArg; else let T be undefined.
      if (arguments.length > 1) {
        T = arguments[1];
      }
  
      // 6. Let A be a new array created as if by the expression new Array(len) 
      //    where Array is the standard built-in constructor with that name and 
      //    len is the value of len.
      A = new Array(len);
  
      // 7. Let k be 0
      k = 0;
  
      // 8. Repeat, while k < len
      while (k < len) {
  
        var kValue, mappedValue;
  
        // a. Let Pk be ToString(k).
        //   This is implicit for LHS operands of the in operator
        // b. Let kPresent be the result of calling the HasProperty internal 
        //    method of O with argument Pk.
        //   This step can be combined with c
        // c. If kPresent is true, then
        if (k in O) {
  
          // i. Let kValue be the result of calling the Get internal 
          //    method of O with argument Pk.
          kValue = O[k];
  
          // ii. Let mappedValue be the result of calling the Call internal 
          //     method of callback with T as the this value and argument 
          //     list containing kValue, k, and O.
          mappedValue = callback.call(T, kValue, k, O);
  
          // iii. Call the DefineOwnProperty internal method of A with arguments
          // Pk, Property Descriptor
          // { Value: mappedValue,
          //   Writable: true,
          //   Enumerable: true,
          //   Configurable: true },
          // and false.
  
          // In browsers that support Object.defineProperty, use the following:
          // Object.defineProperty(A, k, {
          //   value: mappedValue,
          //   writable: true,
          //   enumerable: true,
          //   configurable: true
          // });
  
          // For best browser support, use the following:
          A[k] = mappedValue;
        }
        // d. Increase k by 1.
        k++;
      }
  
      // 9. return A
      return A;
    };
  }
  
  // Production steps of ECMA-262, Edition 5, 15.4.4.15
  // Reference: http://es5.github.io/#x15.4.4.15
  if (!Array.prototype.lastIndexOf) {
    Array.prototype.lastIndexOf = function(searchElement /*, fromIndex*/) {
      'use strict';
  
      if (this === void 0 || this === null) {
        throw new TypeError();
      }
  
      var n, k,
        t = Object(this),
        len = t.length >>> 0;
      if (len === 0) {
        return -1;
      }
  
      n = len - 1;
      if (arguments.length > 1) {
        n = Number(arguments[1]);
        if (n != n) {
          n = 0;
        }
        else if (n != 0 && n != (1 / 0) && n != -(1 / 0)) {
          n = (n > 0 || -1) * Math.floor(Math.abs(n));
        }
      }
  
      for (k = n >= 0 ? Math.min(n, len - 1) : len - Math.abs(n); k >= 0; k--) {
        if (k in t && t[k] === searchElement) {
          return k;
        }
      }
      return -1;
    };
  }
  
  // Production steps of ECMA-262, Edition 5, 15.4.4.21
  // Reference: http://es5.github.io/#x15.4.4.21
  // https://tc39.github.io/ecma262/#sec-array.prototype.reduce
  if (!Array.prototype.reduce) {
    Object.defineProperty(Array.prototype, 'reduce', {
      value: function(callback /*, initialValue*/) {
        if (this === null) {
          throw new TypeError( 'Array.prototype.reduce ' + 
            'called on null or undefined' );
        }
        if (typeof callback !== 'function') {
          throw new TypeError( callback +
            ' is not a function');
        }
  
        // 1. Let O be ? ToObject(this value).
        var o = Object(this);
  
        // 2. Let len be ? ToLength(? Get(O, "length")).
        var len = o.length >>> 0; 
  
        // Steps 3, 4, 5, 6, 7      
        var k = 0; 
        var value;
  
        if (arguments.length >= 2) {
          value = arguments[1];
        } else {
          while (k < len && !(k in o)) {
            k++; 
          }
  
          // 3. If len is 0 and initialValue is not present,
          //    throw a TypeError exception.
          if (k >= len) {
            throw new TypeError( 'Reduce of empty array ' +
              'with no initial value' );
          }
          value = o[k++];
        }
  
        // 8. Repeat, while k < len
        while (k < len) {
          // a. Let Pk be ! ToString(k).
          // b. Let kPresent be ? HasProperty(O, Pk).
          // c. If kPresent is true, then
          //    i.  Let kValue be ? Get(O, Pk).
          //    ii. Let accumulator be ? Call(
          //          callbackfn, undefined,
          //          « accumulator, kValue, k, O »).
          if (k in o) {
            value = callback(value, o[k], k, o);
          }
  
          // d. Increase k by 1.      
          k++;
        }
  
        // 9. Return accumulator.
        return value;
      }
    });
  }
  
  // Production steps of ECMA-262, Edition 5, 15.4.4.22
  // Reference: http://es5.github.io/#x15.4.4.22
  if ('function' !== typeof Array.prototype.reduceRight) {
    Array.prototype.reduceRight = function(callback /*, initialValue*/) {
      'use strict';
      if (null === this || 'undefined' === typeof this) {
        throw new TypeError('Array.prototype.reduce called on null or undefined');
      }
      if ('function' !== typeof callback) {
        throw new TypeError(callback + ' is not a function');
      }
      var t = Object(this), len = t.length >>> 0, k = len - 1, value;
      if (arguments.length >= 2) {
        value = arguments[1];
      } else {
        while (k >= 0 && !(k in t)) {
          k--;
        }
        if (k < 0) {
          throw new TypeError('Reduce of empty array with no initial value');
        }
        value = t[k--];
      }
      for (; k >= 0; k--) {
        if (k in t) {
          value = callback(value, t[k], k, t);
        }
      }
      return value;
    };
  }
  
  if (!Array.isArray) {
    Array.isArray = function(arg) {
      return Object.prototype.toString.call(arg) === '[object Array]';
    };
  }
  /* Simple JavaScript Inheritance
   * By John Resig http://ejohn.org/
   * MIT Licensed.
   */
  // Inspired by base2 and Prototype
  (function() {
      /* istanbul ignore next */
      var initializing = false,
          fnTest = /xyz/.test(function() {
              xyz;
          }) ? /\b_super\b/ : /.*/;
  
      // The base Class implementation (does nothing)
      this.Class = function() {};
  
      // Create a new Class that inherits from this class
      Class.extend = function(prop) {
          var _super = this.prototype;
  
          // Instantiate a base class (but only create the instance,
          // don't run the init constructor)
          initializing = true;
          var prototype = new this();
          initializing = false;
  
          // Copy the properties over onto the new prototype
          for (var name in prop) {
              // Check if we're overwriting an existing function
              prototype[name] = typeof prop[name] == "function" &&
                  typeof _super[name] == "function" && fnTest.test(prop[name]) ?
                  (function(name, fn) {
                      return function() {
                          var tmp = this._super;
  
                          // Add a new ._super() method that is the same method
                          // but on the super-class
                          this._super = _super[name];
  
                          // The method only need to be bound temporarily, so we
                          // remove it when we're done executing
                          var ret = fn.apply(this, arguments);
                          this._super = tmp;
  
                          return ret;
                      };
                  })(name, prop[name]) :
                  prop[name];
          }
  
          // The dummy class constructor
          function Class() {
              // All construction is actually done in the init method
              if (!initializing && this.init)
                  this.init.apply(this, arguments);
          }
  
          // Populate our constructed prototype object
          Class.prototype = prototype;
  
          // Enforce the constructor to be what we expect
          Class.prototype.constructor = Class;
  
          // And make this class extendable
          Class.extend = arguments.callee;
  
          return Class;
      };
  })();
  
  (function(root,factory){if(typeof exports==="object"&&typeof module==="object")module.exports=factory();else if(typeof define==="function"&&define.amd)define("EventBus",[],factory);else if(typeof exports==="object")exports["EventBus"]=factory();else root["EventBus"]=factory()})(this,function(){var EventBusClass={};EventBusClass=function(){this.listeners={}};EventBusClass.prototype={addEventListener:function(type,callback,scope){var args=[];var numOfArgs=arguments.length;for(var i=0;i<numOfArgs;i++){args.push(arguments[i])}args=args.length>3?args.splice(3,args.length-1):[];if(typeof this.listeners[type]!="undefined"){this.listeners[type].push({scope:scope,callback:callback,args:args})}else{this.listeners[type]=[{scope:scope,callback:callback,args:args}]}},removeEventListener:function(type,callback,scope){if(typeof this.listeners[type]!="undefined"){var numOfCallbacks=this.listeners[type].length;var newArray=[];for(var i=0;i<numOfCallbacks;i++){var listener=this.listeners[type][i];if(listener.scope==scope&&listener.callback==callback){}else{newArray.push(listener)}}this.listeners[type]=newArray}},hasEventListener:function(type,callback,scope){if(typeof this.listeners[type]!="undefined"){var numOfCallbacks=this.listeners[type].length;if(callback===undefined&&scope===undefined){return numOfCallbacks>0}for(var i=0;i<numOfCallbacks;i++){var listener=this.listeners[type][i];if((scope?listener.scope==scope:true)&&listener.callback==callback){return true}}}return false},dispatch:function(type,target){var numOfListeners=0;var event={type:type,target:target};var args=[];var numOfArgs=arguments.length;for(var i=0;i<numOfArgs;i++){args.push(arguments[i])}args=args.length>2?args.splice(2,args.length-1):[];args=[event].concat(args);if(typeof this.listeners[type]!="undefined"){var numOfCallbacks=this.listeners[type].length;for(var i=0;i<numOfCallbacks;i++){var listener=this.listeners[type][i];if(listener&&listener.callback){var concatArgs=args.concat(listener.args);listener.callback.apply(listener.scope,concatArgs);numOfListeners+=1}}}},getEvents:function(){var str="";for(var type in this.listeners){var numOfCallbacks=this.listeners[type].length;for(var i=0;i<numOfCallbacks;i++){var listener=this.listeners[type][i];str+=listener.scope&&listener.scope.className?listener.scope.className:"anonymous";str+=" listen for '"+type+"'\n"}}return str}};var EventBus=new EventBusClass;return EventBus});
  /* mousetrap v1.6.0 craig.is/killing/mice */
  (function(r,t,g){function u(a,b,h){a.addEventListener?a.addEventListener(b,h,!1):a.attachEvent("on"+b,h)}function y(a){if("keypress"==a.type){var b=String.fromCharCode(a.which);a.shiftKey||(b=b.toLowerCase());return b}return k[a.which]?k[a.which]:p[a.which]?p[a.which]:String.fromCharCode(a.which).toLowerCase()}function D(a){var b=[];a.shiftKey&&b.push("shift");a.altKey&&b.push("alt");a.ctrlKey&&b.push("ctrl");a.metaKey&&b.push("meta");return b}function v(a){return"shift"==a||"ctrl"==a||"alt"==a||
  "meta"==a}function z(a,b){var h,c,e,g=[];h=a;"+"===h?h=["+"]:(h=h.replace(/\+{2}/g,"+plus"),h=h.split("+"));for(e=0;e<h.length;++e)c=h[e],A[c]&&(c=A[c]),b&&"keypress"!=b&&B[c]&&(c=B[c],g.push("shift")),v(c)&&g.push(c);h=c;e=b;if(!e){if(!n){n={};for(var l in k)95<l&&112>l||k.hasOwnProperty(l)&&(n[k[l]]=l)}e=n[h]?"keydown":"keypress"}"keypress"==e&&g.length&&(e="keydown");return{key:c,modifiers:g,action:e}}function C(a,b){return null===a||a===t?!1:a===b?!0:C(a.parentNode,b)}function c(a){function b(a){a=
  a||{};var b=!1,m;for(m in n)a[m]?b=!0:n[m]=0;b||(w=!1)}function h(a,b,m,f,c,h){var g,e,k=[],l=m.type;if(!d._callbacks[a])return[];"keyup"==l&&v(a)&&(b=[a]);for(g=0;g<d._callbacks[a].length;++g)if(e=d._callbacks[a][g],(f||!e.seq||n[e.seq]==e.level)&&l==e.action){var q;(q="keypress"==l&&!m.metaKey&&!m.ctrlKey)||(q=e.modifiers,q=b.sort().join(",")===q.sort().join(","));q&&(q=f&&e.seq==f&&e.level==h,(!f&&e.combo==c||q)&&d._callbacks[a].splice(g,1),k.push(e))}return k}function g(a,b,m,f){d.stopCallback(b,
  b.target||b.srcElement,m,f)||!1!==a(b,m)||(b.preventDefault?b.preventDefault():b.returnValue=!1,b.stopPropagation?b.stopPropagation():b.cancelBubble=!0)}function e(a){"number"!==typeof a.which&&(a.which=a.keyCode);var b=y(a);b&&("keyup"==a.type&&x===b?x=!1:d.handleKey(b,D(a),a))}function k(a,c,m,f){function e(c){return function(){w=c;++n[a];clearTimeout(r);r=setTimeout(b,1E3)}}function h(c){g(m,c,a);"keyup"!==f&&(x=y(c));setTimeout(b,10)}for(var d=n[a]=0;d<c.length;++d){var p=d+1===c.length?h:e(f||
  z(c[d+1]).action);l(c[d],p,f,a,d)}}function l(a,b,c,f,e){d._directMap[a+":"+c]=b;a=a.replace(/\s+/g," ");var g=a.split(" ");1<g.length?k(a,g,b,c):(c=z(a,c),d._callbacks[c.key]=d._callbacks[c.key]||[],h(c.key,c.modifiers,{type:c.action},f,a,e),d._callbacks[c.key][f?"unshift":"push"]({callback:b,modifiers:c.modifiers,action:c.action,seq:f,level:e,combo:a}))}var d=this;a=a||t;if(!(d instanceof c))return new c(a);d.target=a;d._callbacks={};d._directMap={};var n={},r,x=!1,p=!1,w=!1;d._handleKey=function(a,
  c,e){var f=h(a,c,e),d;c={};var k=0,l=!1;for(d=0;d<f.length;++d)f[d].seq&&(k=Math.max(k,f[d].level));for(d=0;d<f.length;++d)f[d].seq?f[d].level==k&&(l=!0,c[f[d].seq]=1,g(f[d].callback,e,f[d].combo,f[d].seq)):l||g(f[d].callback,e,f[d].combo);f="keypress"==e.type&&p;e.type!=w||v(a)||f||b(c);p=l&&"keydown"==e.type};d._bindMultiple=function(a,b,c){for(var d=0;d<a.length;++d)l(a[d],b,c)};u(a,"keypress",e);u(a,"keydown",e);u(a,"keyup",e)}if(r){var k={8:"backspace",9:"tab",13:"enter",16:"shift",17:"ctrl",
  18:"alt",20:"capslock",27:"esc",32:"space",33:"pageup",34:"pagedown",35:"end",36:"home",37:"left",38:"up",39:"right",40:"down",45:"ins",46:"del",91:"meta",93:"meta",224:"meta"},p={106:"*",107:"+",109:"-",110:".",111:"/",186:";",187:"=",188:",",189:"-",190:".",191:"/",192:"`",219:"[",220:"\\",221:"]",222:"'"},B={"~":"`","!":"1","@":"2","#":"3",$:"4","%":"5","^":"6","&":"7","*":"8","(":"9",")":"0",_:"-","+":"=",":":";",'"':"'","<":",",">":".","?":"/","|":"\\"},A={option:"alt",command:"meta","return":"enter",
  escape:"esc",plus:"+",mod:/Mac|iPod|iPhone|iPad/.test(navigator.platform)?"meta":"ctrl"},n;for(g=1;20>g;++g)k[111+g]="f"+g;for(g=0;9>=g;++g)k[g+96]=g;c.prototype.bind=function(a,b,c){a=a instanceof Array?a:[a];this._bindMultiple.call(this,a,b,c);return this};c.prototype.unbind=function(a,b){return this.bind.call(this,a,function(){},b)};c.prototype.trigger=function(a,b){if(this._directMap[a+":"+b])this._directMap[a+":"+b]({},a);return this};c.prototype.reset=function(){this._callbacks={};this._directMap=
  {};return this};c.prototype.stopCallback=function(a,b){return-1<(" "+b.className+" ").indexOf(" mousetrap ")||C(b,this.target)?!1:"INPUT"==b.tagName||"SELECT"==b.tagName||"TEXTAREA"==b.tagName||b.isContentEditable};c.prototype.handleKey=function(){return this._handleKey.apply(this,arguments)};c.addKeycodes=function(a){for(var b in a)a.hasOwnProperty(b)&&(k[b]=a[b]);n=null};c.init=function(){var a=c(t),b;for(b in a)"_"!==b.charAt(0)&&(c[b]=function(b){return function(){return a[b].apply(a,arguments)}}(b))};
  c.init();r.Mousetrap=c;"undefined"!==typeof module&&module.exports&&(module.exports=c);"function"===typeof define&&define.amd&&define(function(){return c})}})("undefined"!==typeof window?window:null,"undefined"!==typeof window?document:null);
  
  /**
   * @author Santhosh Vasabhaktula <santhosh@ilimi.in>
   */
  
  // Declare Namespace
  window.org = { ekstep: {} }
  
  var plugin_framework = function() {};
  plugin_framework.prototype.initialize = function(config) {
      config = config || {};
      org.ekstep.pluginframework.config = org.ekstep.pluginframework.config || {};
      if (!config.env) throw "Framework should be initialized with environment!";
      org.ekstep.pluginframework.env = config.env;
      org.ekstep.pluginframework.jQuery = config.jQuery || window.$;
      org.ekstep.pluginframework.async = config.async || window.async;
      org.ekstep.pluginframework.config.build_number = config.build_number || 'BUILD_NUMBER';
      org.ekstep.pluginframework.config.pluginRepo = config.pluginRepo || '/content-plugins';
  };
  
  window.org.ekstep.pluginframework = new plugin_framework();
  plugin_framework = undefined;
  
  var services_framework = function() {};
  window.org.ekstep.services = new services_framework();
  services_framework = undefined;
  /**
   * @author Harish kumar Gangula<harishg@ilimi.in>
   */
  org.ekstep.pluginframework.resourceManager = new(Class.extend({
      init: function() {},
      buildNumber: undefined,
      registeredRepos: [],
      initialize: function() {},
      discoverManifest: function(pluginId, pluginVer, cb, publishedTime) {
          var ayncTasks = [];
  
          this.registeredRepos.forEach(function(repo, index) {
              var Fns = function() {
                  if (index == 0) {
                      return function(callback) {
                          repo.discoverManifest(pluginId, pluginVer, callback, publishedTime);
                      }
                  } else {
                      return function(data, callback) {
                          if (data.manifest == undefined) {
                              repo.discoverManifest(pluginId, pluginVer, callback, publishedTime);
                          } else {
                              callback(null, data);
                          }
                      }
                  }
              };
  
              ayncTasks.push(Fns());
          });
  
          org.ekstep.pluginframework.async.waterfall(ayncTasks, function(err, result) {
              if (result.manifest !== undefined)
                  cb(undefined, result);
              else
                  cb('Plugin not found in any repo or manifest', undefined);
          });
  
      },
      addRepo: function(repo, position) {
          var repoFound = this.registeredRepos.find(function(rp) {
              return rp.id == repo.id;
          });
  
          if (!repoFound) {
              if (typeof position === 'number') this.registeredRepos.splice(position, 0, repo)
              else this.registeredRepos.push(repo);
          } else {
              console.error(repo.id + ': Repo already registered!');
          }
      },
      getResource: function(pluginId, pluginVer, src, dataType, repo, callback, publishedTime) {
          var resource = repo.resolveResource(pluginId, pluginVer, src);
          this.loadResource(resource, dataType, callback, publishedTime);
      },
      loadExternalPluginResource: function(type, pluginId, pluginVer, src, repo, publishedTime, callback) {
          var resource = repo.resolveResource(pluginId, pluginVer, src) + "?" + (publishedTime || "");
          this.loadExternalResource(resource, type, publishedTime, callback);
      },
      loadExternalResource: function(resource, type, publishedTime, callback) {
          switch (type) {
              case 'js':
                  if (callback)
                      this.loadResource(resource, 'script', callback, publishedTime);
                  else
                      org.ekstep.pluginframework.jQuery("body").append($("<script type='text/javascript' src=" + resource + ">"));
                  break;
              case 'css':
                  org.ekstep.pluginframework.jQuery("head").append("<link rel='stylesheet' type='text/css' href='" + resource + "'>");
                  if (callback) callback();
                  break;
              default:
                  if (callback) callback();
          }
      },
      loadResource: function(url, dataType, callback, publishedTime) {
          url = url + "?" + (org.ekstep.pluginframework.config ? org.ekstep.pluginframework.config.build_number : '');
          if (publishedTime) {
              url = url + "&" + publishedTime;
          }
          org.ekstep.pluginframework.jQuery.ajax({
              async: false,
              url: url,
              dataType: dataType
          }).done(function(data) {
              callback(null, data);
          }).fail(function(jqXHR, textStatus, errorThrown) {
              if(jqXHR.statusText === 'OK') {
                  console.log('Unable to load resource:', url, 'error:', errorThrown);
              }
              callback(errorThrown)
          });
      }
  }));
  
  /**
   * @author Santhosh Vasabhaktula <santhosh@ilimi.in>
   */
  org.ekstep.pluginframework.eventManager = new (Class.extend({
    enableEvents: true,
    addEventListener: function(type, callback, scope) {
      EventBus.addEventListener(type, callback, scope)
    },
    dispatchEvent: function(type, data, target) {
      if(this.enableEvents) EventBus.dispatch(type, target, data);
    },
    removeEventListener: function(type, callback, scope) {
      EventBus.removeEventListener(type, callback, scope);
    }
  }));
  /**
   * @author Santhosh Vasabhaktula <santhosh@ilimi.in>
   */
  org.ekstep.pluginframework.pluginManager = new(Class.extend({
      pluginManifests: {},
      plugins: {},
      pluginObjs: {},
      pluginInstances: {},
      pluginVisited: {},
      errors: [],
      init: function() {
          console.log("Plugin manager initialized");
      },
      _registerPlugin: function(pluginId, pluginVer, plugin, manifest, repo) {
          this.plugins[pluginId] = { p: plugin, m: manifest, repo: repo };
          this._registerNameSpace(pluginId, plugin);
          if (manifest) this.pluginManifests[manifest.id] =  manifest;
          org.ekstep.pluginframework.eventManager.dispatchEvent('plugin:load', { plugin: pluginId, version: pluginVer });
          org.ekstep.pluginframework.eventManager.dispatchEvent(pluginId + ':load');
          var p = new plugin(manifest); 
          if (manifest) this.pluginObjs[manifest.id] = p;                
      },
      registerPlugin: function(manifest, plugin, repo) {
          repo = repo || org.ekstep.pluginframework.publishedRepo;
          this._registerPlugin(manifest.id, manifest.ver, plugin, manifest, repo);        
      },
      loadCustomPlugin: function(dependency, callback, publishedTime) {
          var instance = this;
          org.ekstep.pluginframework.resourceManager.loadResource(dependency.src, 'text', function(err, data) {
              if (err) {
                  console.error('Unable to load editor plugin', 'plugin:' + dependency.id + '-' + dependency.ver, 'resource:', 'Error:', err);
              } else {
                  try {
                      if (!instance.isPluginDefined(dependency.id)) {
                          data = eval(data);
                          instance._registerPlugin(dependency.id, undefined, data, undefined, undefined);
                      } else {
                          console.info("Plugin is already registered: ", dependency.id);
                      }
                  } catch (e) {
                      console.error("Error while loading plugin", 'plugin:' + dependency.id + '-' + dependency.ver, 'Error:', e);
                  }
              }
              callback && callback();
          }, publishedTime);
      },
      loadPluginByManifest: function(manifest, repo, pluginType, publishedTime) {
          var instance = this;
          var scope = org.ekstep.pluginframework.env;
          org.ekstep.pluginframework.resourceManager.getResource(manifest.id, manifest.ver, manifest[scope].main, 'text', repo, function(err, data) {
              if (err) {
                  console.error('Unable to load editor plugin', 'plugin:' + manifest.id + '-' + manifest.ver, 'resource:' + manifest[scope].main, 'Error:', err);
              } else {
                  try {
                      if (!instance.isPluginDefined(manifest.id)) {
                          if (pluginType == 'library') {
                              org.ekstep.pluginframework.jQuery.globalEval(data);
                          } else {
                              if (data) instance.registerPlugin(manifest, eval(data), repo);
                          }
                      } else {
                          console.info("Plugin is already registered: ", manifest.id);
                      }
                  } catch (e) {
                      console.error("Error while loading plugin", 'plugin:' + manifest.id + '-' + manifest.ver, 'Error:', e);
                  }
              }
          }, publishedTime);
      },
      _registerNameSpace: function(pluginId, clazz) {
          var names = pluginId.split('.')
          var baseNameSpace = names[0];
          var lastKey = names[names.length-1];
          names.splice(0, 1);
  
          var pluginClazz = (org.ekstep.pluginframework.env === 'editor') ? Class.extend({
              init: function(data, parent, override) {
                  org.ekstep.pluginframework.pluginManager.invoke(pluginId, data, parent, override);
              }
          }) : Class.extend({
              init: function(data, parent, stage, theme) {
                  org.ekstep.pluginframework.pluginManager.invokeRenderer(pluginId, data, parent, stage, theme);
              }
          });
          pluginClazz.extend = function(subClazz) {
              return clazz.extend(subClazz);
          }
  
          if(names.length > 0) {
              if (!window[baseNameSpace]) {
                  window[baseNameSpace] = {};
              }
              names.reduce(function(o, s) {
                  var val = ((s === lastKey) ? pluginClazz : {});
                  return (o[s] === undefined) ? o[s] = val : o[s] 
              }, window[baseNameSpace]);
          } else {
              if(window[baseNameSpace] === undefined) window[baseNameSpace] = pluginClazz;
          }
      },
      loadAndInitPlugin: function(pluginId, version, publishedTime, parent) {
          this.loadPluginWithDependencies(pluginId, version, undefined, publishedTime, function() {});
          if (this.isPluginDefined(pluginId)) {
              var pluginManifest = this.getPluginManifest(pluginId);
              if (pluginManifest.type && (pluginManifest.type.toLowerCase() === "widget")) {
                  this.invoke(pluginId, JSON.parse(JSON.stringify(pluginManifest[org.ekstep.pluginframework.env]['init-data'] || {})), parent);
              }
              return 0;
          } else {
              return 1;
          }
      },
      loadPluginWithDependencies: function(pluginId, pluginVer, pluginType, publishedTime, callback) {
          var instance = this;
          if (this.plugins[pluginId] || this.pluginVisited[pluginId]) {
              console.log('A plugin with id "' + pluginId + '" and ver "' + pluginVer + '" is already loaded');
              callback();
          } else {
              org.ekstep.pluginframework.resourceManager.discoverManifest(pluginId, pluginVer, function(err, data) {
                  if (err || (data == undefined)) {
                      console.error('Unable to load plugin manifest', 'plugin:' + pluginId + '-' + pluginVer, 'Error:', err);
                      callback(); //  pass the error
                  } else {
                      instance.pluginVisited[pluginId] = true;                                      
                      instance.loadManifestDependencies(data.manifest.dependencies, publishedTime, function() {
                          if (pluginType === 'renderer') {
                              callback();
                          } else {
                              var queue = instance.queueDependencies(data.manifest, data.repo, publishedTime);
                              console.log('queue.length', queue.length());
                              if (queue.length() > 0) {
                                  queue.drain = function() {
                                      instance.loadPluginByManifest(data.manifest, data.repo, pluginType, publishedTime);
                                      callback();
                                  };
                              } else {
                                  instance.loadPluginByManifest(data.manifest, data.repo, pluginType, publishedTime);
                                  callback();
                              }
                          }
                      });
                  }
              }, publishedTime);
          }
      },
      queueDependencies: function(manifest, repo, publishedTime) {
          var scope = org.ekstep.pluginframework.env;
          var queue = org.ekstep.pluginframework.async.queue(function(task, callback) {
              if (task.type == 'plugin') {
                  if (org.ekstep.pluginframework.env == 'renderer') {
                      instance.loadCustomPlugin({id: task.plugin, src: task.repo.resolveResource(task.id, task.ver, task.src)}, callback, undefined);
                  } else {
                      instance.loadPluginWithDependencies(task.plugin, task.ver, task.type, publishedTime, callback);
                  }
              } else {
                  org.ekstep.pluginframework.resourceManager.loadExternalPluginResource(task.type, task.id, task.ver, task.src, task.repo, task.publishedTime, callback);
              }
          }, 1);
          var instance = this;
          if (Array.isArray(manifest[scope].dependencies)) {
              manifest[scope].dependencies.forEach(function(dependency) {
                  if (dependency.type == 'plugin') {
                      if (org.ekstep.pluginframework.env == 'renderer') {
                          queue.push({
                              type: dependency.type,
                              id: manifest.id,
                              ver: manifest.ver,
                              src: dependency.src,
                              repo: repo,
                              plugin: dependency.id
                          }, function() {});
                      } else {
                          queue.push(dependency, function() {});
                      }
                  } else if(dependency.type == 'js' || dependency.type == 'css') {
                      queue.push({
                          type: dependency.type,
                          id: manifest.id,
                          ver: manifest.ver,
                          src: dependency.src,
                          repo: repo,
                          publishedTime: publishedTime
                      }, function() {});
                  }
              });
          }
          return queue;
      },
      loadManifestDependencies: function(dependencies, publishedTime, callback) {
          var instance = this;
          if (Array.isArray(dependencies) && dependencies.length > 0) {
              var queue = org.ekstep.pluginframework.async.queue(function(plugin, pluginCallback) {
                  instance.loadPluginWithDependencies(plugin.id, plugin.ver, plugin.type, plugin.pt, pluginCallback);
              }, 1);
              dependencies.forEach(function(dep) {
                  if (org.ekstep.pluginframework.env == 'renderer') {
                      if (dep.scope == org.ekstep.pluginframework.env || dep.scope == 'all') {
                          queue.push({ 'id': dep.plugin, 'ver': dep.ver, 'type': dep.type, 'pt': publishedTime }, function(err) {});
                      }
                  } else {
                      queue.push({ 'id': dep.plugin, 'ver': dep.ver, 'type': dep.type, 'pt': publishedTime }, function(err) {});
                  }
              });
              if (queue.length() > 0) {
                  queue.drain = function() {
                      callback();
                  };
              } else {
                  callback();
              }
          } else {
              callback();
          }
      },
      isManifestDefined: function(id) {
          if (this.pluginManifests[id]) {
              return true;
          } else {
              return false;
          }
      },
      isPluginDefined: function(id) {
          if (this.plugins[id]) {
              return true;
          } else {
              return false;
          }
      },
      loadPlugin: function(pluginId, pluginVer, callback) {
          this.loadPluginWithDependencies(pluginId, pluginVer, "plugin", undefined, function() {
             console.log('Plugin loaded: ', pluginId); 
             callback && callback();
          });
      },
      loadAllPlugins: function(plugins, otherDependencies, callback) {
          var instance = this;
          if (Array.isArray(plugins) && plugins.length) {
              var preloadPlugin = plugins.find(function(plugin) {
                  return (plugin.preload === true || plugin.preload === 'true');
              });
              if(preloadPlugin) {
                  instance.loadPlugin(preloadPlugin.id, preloadPlugin.ver, function() {
                      instance._loadPlugins(plugins, otherDependencies, callback);
                  })
              } else {
                  instance._loadPlugins(plugins, otherDependencies, callback);
              }
          } else if (Array.isArray(otherDependencies) && otherDependencies.length) {
              instance.loadOtherDependencies(otherDependencies, callback);
          } else {
              callback();
          }
      },
      _loadPlugins: function(plugins, otherDependencies, callback) {
          var instance = this;
          var q = org.ekstep.pluginframework.async.queue(function(plugin, pluginCallback) {
              instance.loadPluginWithDependencies(plugin.id, plugin.ver, plugin.type, plugin.pt, pluginCallback);
          }, 6);
          q.drain = function() {
              instance.loadOtherDependencies(otherDependencies, callback);
          };
          plugins.forEach(function(plugin) {
              q.push({ 'id': plugin.id, 'ver': plugin.ver, 'type': plugin.type, 'pt': undefined }, function(err) {});
          });
      },
      loadOtherDependencies: function(otherDependencies, callback) {
          var instance = this;
          if (Array.isArray(otherDependencies) && otherDependencies.length) {
              var queue = org.ekstep.pluginframework.async.queue(function(dependency, cb) {
                  if (dependency.type == 'plugin') {
                      instance.loadCustomPlugin(dependency, cb);
                  } else {
                      org.ekstep.pluginframework.resourceManager.loadExternalResource(dependency.src, dependency.type, undefined, cb);
                  }
              }, 1);
              otherDependencies.forEach(function(dep) {
                  queue.push(dep, function(err) {});
              });
              if (queue.length() > 0) {
                  queue.drain = function() {
                      callback && callback();
                  };
              } else {
                  callback && callback();
              }
          } else {
              callback && callback();
          }
      },
      invoke: function(id, data, parent, override) {
          var instance = this;
          var p = undefined;
          var plugin = this.plugins[id];
          if (!plugin) {
              this.addError('No plugin found for - ' + id);
          } else {
              var pluginClass = override ? plugin.p.extend(override) : plugin.p;
              var pluginManifest = plugin.m;
              try {
                  if (Array.isArray(data)) {
                      data.forEach(function(d) {
                          p = new pluginClass(pluginManifest, d, parent);
                          instance.addPluginInstance(p);
                          p.initPlugin();
                          org.ekstep.pluginframework.eventManager.dispatchEvent('plugin:add', { plugin: pluginManifest.id, version: pluginManifest.ver, instanceId: p.id });
                          org.ekstep.pluginframework.eventManager.dispatchEvent(pluginManifest.id + ':add');
                      })
                  } else {
                      p = new pluginClass(pluginManifest, data, parent);
                      instance.addPluginInstance(p);
                      p.initPlugin();
                      org.ekstep.pluginframework.eventManager.dispatchEvent('plugin:add', { plugin: pluginManifest.id, version: pluginManifest.ver, instanceId: p.id });
                      org.ekstep.pluginframework.eventManager.dispatchEvent(pluginManifest.id + ':add');
                  }
              } catch (e) {
                  delete instance.pluginInstances[p.id];
                  throw e;
              }
          }
          return p;
      },
      invokeRenderer: function(id, data, parent, stage, theme) {
          var instance = this;
          var p = undefined;
          var plugin = this.plugins[id];
          if (!plugin) {
              this.addError('No plugin found for - ' + id);
          } else {
              try {
                  var pluginClass = plugin.p;
                  var pluginManifest = plugin.m || { id: id, ver: undefined };
                  if (Array.isArray(data)) {
                      data.forEach(function(d) {
                          p = new pluginClass(d, parent, stage, theme);
                          instance.addPluginInstance(p);
                          org.ekstep.pluginframework.eventManager.dispatchEvent('plugin:add', { plugin: pluginManifest.id, version: pluginManifest.ver, instanceId: p.id });
                          org.ekstep.pluginframework.eventManager.dispatchEvent(pluginManifest.id + ':add');
                      })
                  } else {
                      p = new pluginClass(data, parent, stage, theme);
                      instance.addPluginInstance(p);
                      org.ekstep.pluginframework.eventManager.dispatchEvent('plugin:add', { plugin: pluginManifest.id, version: pluginManifest.ver, instanceId: p.id });
                      org.ekstep.pluginframework.eventManager.dispatchEvent(pluginManifest.id + ':add');
                  }
              } catch (e) {
                  delete instance.pluginInstances[p.id];
                  throw e;
              }
          }
          return p;
      },
      addPluginInstance: function(pluginObj) {
          this.pluginInstances[pluginObj.id] = pluginObj;
      },
      removePluginInstance: function(pluginObj) {
          /* istanbul ignore else */
          if (pluginObj) pluginObj.remove();
      },
      getPluginInstance: function(id) {
          return this.pluginInstances[id];
      },
      getPluginInstances: function() {
          return this.pluginInstances;
      },
      getPluginManifest: function(id) {
          var plugin = this.plugins[id];
          if (plugin) {
              return plugin.m;
          } else {
              return undefined;
          }
      },
      addError: function(error) {
          this.errors.push(error);
      },
      getErrors: function() {
          return this.errors;
      },
      cleanUp: function() {
          this.pluginInstances = {};
          this.pluginManifests = {};
          this.pluginVisited = {};
          this.plugins = {};
          this.errors = [];
      },
      getPlugins: function() {
          return Object.keys(this.plugins);
      },
      getPluginType: function(id) {
          if (this.pluginInstances[id]) {
              return this.pluginInstances[id].getType();
          } else {
              return '';
          }
      },
      loadPluginResource: function(pluginId, pluginVer, src, dataType, callback) {
          if (this.plugins[pluginId]) {
              org.ekstep.pluginframework.resourceManager.getResource(pluginId, pluginVer, src, dataType, this.plugins[pluginId]['repo'], callback)
          } else {
              callback(new Error("unable load plugin resource " + src), undefined)
          }
      },
      getPluginVersion: function(id) {
          if (this.pluginInstances[id]) {
              return this.pluginInstances[id].getVersion();
          } else {
              return '';
  
          }
      },
      resolvePluginResource: function(id, ver, resource) {
          if (this.plugins[id] && this.plugins[id]["repo"]) {
              return this.plugins[id]["repo"].resolveResource(id, ver, resource);
          } else {
              return false;
          }
      }
  }));
  
  /**
   * @author Santhosh Vasabhaktula <santhosh@ilimi.in>
   */
  org.ekstep.pluginframework.keyboardManager = new (Class.extend({
    registry: {},
    initialize: function() {},
    registerKeyCombination: function(command, callback) {
      if(command !== undefined && callback !== undefined){
        Mousetrap.bind(command, callback);
      } else {
        throw "The given key combination is invalid.";
      }
    },
    resolveKeyCombination: function(event) {
      // wrap Mousetrap here..
    }
  }));
  /* istanbul ignore next */
  org.ekstep.services.iService = Class.extend({
      getBaseURL: function() {
          return org.ekstep.services.config.baseURL;
      },
      getAPISlug: function() {
          return org.ekstep.services.config.apislug;
      },
      init: function(config) {
          this.initService(config);
      },
      initService: function(config) {},                
      _dispatchTelemetry: function(data) {
          var status = data.res.responseCode || data.res.statusText;
          org.ekstep.services.telemetryService.apiCall({ "path": data.url, "method": data.method, "request": data.request, "response": "", "responseTime": data.res.responseTime, "status": status, "uip": "" });
      },
      get: function(url, config, cb) {
          var requestTimestamp, instance = this;
          config = config || {};
          config.headers = config.headers || {};
          if (typeof cb !== 'function') throw "iservice expects callback to be function";
          org.ekstep.pluginframework.jQuery.ajax({
              type: "GET",
              url: url,
              headers: config.headers,
              beforeSend: function(xhrObject, settings) {
                  requestTimestamp = (new Date()).getTime();
              },
              success: function(res) {
                  res.responseTime = (new Date()).getTime() - requestTimestamp;
                  instance._dispatchTelemetry({url: url, method: "GET", request: "", res: res }); 
                  res = { data: res };
                  cb(null, res);                
              },
              error: function(err) {
                  err.responseTime = (new Date()).getTime() - requestTimestamp;
                  cb(err, null);
                  instance._dispatchTelemetry({url: url, method: "GET", request: "", res: err });
              }
          });
      },
      post: function(url, data, config, cb) {
          var requestTimestamp, instance = this;
          data = data || {};
          config = config || {};
          config.headers = config.headers || {};
          if (typeof cb !== 'function') throw "iservice expects callback to be function";
          if (typeof data === 'object') data = JSON.stringify(data);
          org.ekstep.pluginframework.jQuery.ajax({
              type: "POST",
              url: url,
              data: data,
              headers: config.headers,
              beforeSend: function(xhrObject, settings) {
                  requestTimestamp = (new Date()).getTime();
              },
              success: function(res) {
                  res.responseTime = (new Date()).getTime() - requestTimestamp;
                  instance._dispatchTelemetry({url: url, method: "POST", request: data, res: res }); 
                  res = { data: res };
                  cb(null, res);                
              },
              error: function(err) {
                  err.responseTime = (new Date()).getTime() - requestTimestamp;
                  cb(err, null);
                  instance._dispatchTelemetry({url: url, method: "POST", request: data, res: err });
              }
          });
      },
      patch: function(url, data, config, cb) {
          var requestTimestamp, instance = this;
          data = data || {};
          config = config || {};
          config.headers = config.headers || {};
          if (typeof cb !== 'function') throw "iservice expects callback to be function";
          if (typeof data === 'object') data = JSON.stringify(data);
          org.ekstep.pluginframework.jQuery.ajax({
              type: "PATCH",
              url: url,
              data: data,
              headers: config.headers,
              beforeSend: function(xhrObject, settings) {
                  requestTimestamp = (new Date()).getTime();
              },
              success: function(res) {
                  res.responseTime = (new Date()).getTime() - requestTimestamp;
                  instance._dispatchTelemetry({url: url, method: "PATCH", request: data, res: res });
                  res = { data: res };
                  cb(null, res);                
              },
              error: function(xhr, status, error) {
                  xhr.responseTime = (new Date()).getTime() - requestTimestamp;
                  cb(xhr, null);
                  instance._dispatchTelemetry({url: url, method: "PATCH", request: data, res: xhr });
              }
          });
      },
      /**
       * Utility function which is used to call http post request
       * @param  {string}   url      API url
       * @param  {object}   data     APT request data
       * @param  {object}   headers  API headers
       * @param  {Function} callback returns error and response as arguments
       * @memberof org.ekstep.services.iService
       */
      postFromService: function(url, data, headers, callback) {
          this.post(url, JSON.stringify(data), headers, function(err, res) {
              callback(err, res)
          });
      },
      /**
       * Utility function which is used to call http get request
       * @param  {string}   url      API url
       * @param  {object}   headers  API headers
       * @param  {Function} callback returns error and response as arguments
       * @memberof org.ekstep.services.iService
       */
      getFromService: function(url, headers, callback) {
          this.get(url, headers, function(err, res) {
              callback(err, res);
          });
      }
  
  });
  
  /**
   * Content service helps to retrieve/save the content, content meta details by making call to learning API.
   * It also helps to download the content. 
   * 
   * @class org.ekstep.services.contentService
   * @author Sunil A S <sunils@ilimi.in>
   */
  org.ekstep.services.contentService = new(org.ekstep.services.iService.extend({
      serviceURL: function() {
          return this.getBaseURL() + this.getAPISlug() + '/learning/'
      },
      content: {},
      initService: function() {},
      /**
      *
      * content meta data fields
      *
      * @memberof org.ekstep.services.contentService
      */
      contentFields: "body,editorState,stageIcons,templateId,languageCode,template,gradeLevel,status,concepts,versionKey,name,appIcon,contentType,owner,domain,code,visibility,portalOwner,description,language,mediaType,mimeType,osId,languageCode,createdOn,lastUpdatedOn",
      requestHeaders: {
          "headers": {
              "content-type": "application/json",
              "user-id": "content-editor"
          }
      },
      /**
      * 
      * sets content meta for the given content id
      * @param id {string}
      * @param contentMeta {object} content meta object
      * @private
      * @memberof org.ekstep.services.contentService
      */
      _setContentMeta: function(id, contentMeta) {
          /* istanbul ignore else */
          if (id && contentMeta) {
              var meta = {};
              for(k in contentMeta) {
                  if(k != 'body' && k != 'stageIcons') {
                      meta[k] = contentMeta[k];
                  }
              }
              this.content[id] = meta;
          }
      },
      /**
      *
      * returns content meta details
      * @param id {string} content id
      * @returns {object} if id is "undefined" returns empty object
      *
      * @memberof org.ekstep.services.contentService
      */
      getContentMeta: function(id) {
          return this.content[id] || {};
      },
      /**
      *
      * saves content body by making call to learing API 
      * @param contentId {string} content id
      * @param metadata {object} meta data object
      * @param body {object} ECML JSON object of content
      * @param callback {function} callback function 
      *
      * @memberof org.ekstep.services.contentService
      */
      saveContent: function(contentId, metadata, body, callback) {
          this._saveContent(contentId, metadata, body, callback);
      },
      /**
      *
      * saves content body by making call to learing API 
      * @param contentId {string} content id
      * @param metadata {object} meta data object
      * @param body {object} ECML JSON object of content
      * @param callback {function} callback function 
      * @private
      * @memberof org.ekstep.services.contentService
      *
      */
      _saveContent: function(contentId, metadata, body, callback) {
  
          var instance = this;
          var versionKey = instance.content[contentId] && instance.content[contentId].versionKey;        
  
          if (contentId && versionKey) {
              var update = false;
              var content = {
                  versionKey: versionKey,
                  lastUpdatedBy: window.context.user.id                
              }
              if (metadata) {
                  update = true;
                  for (k in metadata) {
                      content[k] = metadata[k];
                  }
              }
              if (body) {
                  content.compatibilityLevel = body.theme.compatibilityVersion;
                  content['body'] = JSON.stringify(body);
                  update = true;
              }
              if (update) {
                  var headers = { "headers": { "content-type": "application/json", "user-id": "ATTool" } }
                  var requestObj = { request: { content: content } };
                  instance.patch(this.serviceURL() + 'v2/content/' + contentId, requestObj, headers, function(err, res) {
                      /* istanbul ignore else */
                      if (res && res.data.responseCode == "OK") {                        
                          instance.content[contentId].versionKey = res.data.result.versionKey;
                          callback(undefined, res);                        
                      } else {
                          callback(true, err);
                      } 
                  });
              } else {
                  callback('Nothing to save');
              }
          } else {
              callback('Cannot find content id or version key to update content');
          }
      },
      /**
      *
      *
      * retrieves the content and content meta details
      * @param contentId {string} content id
      * @param callback {function} callback function
      *
      * @memberof org.ekstep.services.contentService
      */
      getContent: function(contentId, callback) {
          var instance = this;
          if (contentId) {
              var metaDataFields = "?mode=edit&fields=" + instance.contentFields;
              instance.get(this.serviceURL() + 'v2/content/' + contentId + metaDataFields, {}, function(err, res) {
                  /* istanbul ignore else */
                  if (err) callback(err, undefined);
                  if (!err && res.data && res.data.result && res.data.result.content) {
                      instance._setContentMeta(contentId, res.data.result.content);
                      callback(err, res.data.result.content);
                  } else {
                      callback(new Error('no content found!'), undefined)
                  }
  
              });
          } else {
              callback('Content id is required to get content from platform', undefined);
          }
      },
      /**
      *
      *
      * retrieves the versionKey
      * @param contentId {string} content id
      * @param callback {function} callback function
      *
      * @memberof org.ekstep.services.contentService
      */
      getContentVersionKey: function(contentId, callback) {
          var instance = this;
          if (contentId) {
              var metaDataFields = "?mode=edit&fields=" + "versionKey";
              instance.get(this.serviceURL() + 'v2/content/' + contentId + metaDataFields, {}, function(err, res) {
                  /* istanbul ignore else */
                  if (err) callback(err, undefined);
                  if (!err && res.data && res.data.result && res.data.result.content) {
                      instance._setContentMeta(contentId, res.data.result.content);
                      callback(err, res.data.result.content);
                  } else {
                      callback(new Error('no content found!'), undefined)
                  }
  
              });
          } else {
              callback('Content id is required to get versionKey from platform', undefined);
          }
      },
      /**
       * retrieves template data of selected templateid
       * @param templateId {string} template id
       * @param callback {function} callback function
       * @memberof org.ekstep.services.contentService
       */
      getTemplateData: function(templateId, callback){
          var instance = this;
          var templateMetaFields = "?taxonomyId=literacy_v2&fields=body,editorState,templateId,languageCode";
          instance.get(this.serviceURL() + 'v2/content/' + templateId + templateMetaFields, this.requestHeaders, function(err, res) {
              callback(err, res)
          });
      },
      /**
      *
      *
      * retrieves downloadable URL link to content
      * @param contentId {string} content id
      * @param fileName {string} "name" parameter of meta data object
      * @param callback {function} callback function
      *
      * @memberof org.ekstep.services.contentService
      */
      downloadContent: function(contentId, fileName, callback) {
          var data = { "request": { "content_identifiers": [contentId], "file_name": fileName } };
          this.postFromService(this.serviceURL() + 'v2/content/bundle', data, this.requestHeaders, callback);
      }
  }));
  
  /**
   *
   * Assessment service helps to get questions(items)
   * @class org.ekstep.services.assessmentService
   * @author Santhosh Vasabhaktula <santhosh@ilimi.in>
   */
  org.ekstep.services.assessmentService = new(org.ekstep.services.iService.extend({
      /** 
       * @member {string} learningURL
       * @memberof org.ekstep.services.assessmentService
       */
      learningURL: function() {
          return this.getBaseURL() + this.getAPISlug() + '/learning/'
      },
       /** 
       * @member {object} requestHeaders
       * @memberof org.ekstep.services.assessmentService
       */
      requestHeaders: {
          "headers": {
              "content-type": "application/json",
              "user-id": "content-editor"
          }
      },
      /**
       * Get Questions from search API
       * @param  {object}   data     search filter data
       * @param  {Function} callback returns error and response as arguments
       * @memberof org.ekstep.services.assessmentService
       */
      getQuestions: function(data, callback) {
          org.ekstep.services.searchService.search(data, callback);
      },
      /**
       * Get selected Question(assessmentitem)
       * @param  {string}   itemId   selected question(assessmentitem) id
       * @param  {Function} callback returns error and response as arguments
       * @memberof org.ekstep.services.assessmentService
       */
      getItem: function(itemId, callback) {
          this.getFromService(this.learningURL() + 'v1/assessmentitem/' + itemId, this.requestHeaders, callback);
      },
      /**
       * Get template data of selected question from content service API
       * @param  {string}   templateId selected question(assessmentitem) template id
       * @param  {Function} callback   returns error and response as arguments
       * @memberof org.ekstep.services.assessmentService
       */
      getTemplate: function(templateId, callback) {
          org.ekstep.services.contentService.getTemplateData(templateId, callback);
      }
  }));
  
  /**
   * Asset service provides access to the content API to save assets.
   * @class org.ekstep.services.assetService
   * @author Santhosh Vasabhaktula <santhosh@ilimi.in>
   */
  org.ekstep.services.assetService = new(org.ekstep.services.iService.extend({
       /** 
       * @member {string} searchURL
       * @memberof org.ekstep.services.assetService
       */
      learningURL: function() {
          return this.getBaseURL() + this.getAPISlug() + '/learning/'
      },
      asset: {},
       /** 
       * @member {object} requestHeaders
       * @memberof org.ekstep.services.assetService
       */
      requestHeaders: {
          "headers": {
              "content-type": "application/json",
              "user-id": "content-editor"
          }
      },
      initService: function() {},
      /**
       * Set asset object
       * @param {string} id        
       * @param {object} assetMeta 
       * @memberof org.ekstep.services.assetService
       */
      setAssetMeta: function(id, assetMeta) {
          if (id && assetMeta) {
              if(this.asset[id] == undefined) this.asset[id] = {};
              this.asset[id].assetMeta = assetMeta;
          }
      },
      /**
       * get asset object
       * @param  {string} id 
       * @memberof org.ekstep.services.assetService
       */
      getAssetMeta: function(id) {
          return this.asset[id] || {};
      },
      /**
       * This method is used to save assets(audio & image)
       * @param  {string}   assetId
       * @param  {string}   content
       * @param  {Function} callback returns error and response as arguments
       * @memberof org.ekstep.services.assetService
       */
      saveAsset: function(assetId, content, callback) {
          var instance = this;
  
          var requestObj = {
              request: {
                  content: content
              }
          };
          if (assetId) {
              instance.patch(this.learningURL() + 'v2/content/', requestObj, this.requestHeaders, function(err, res) {
                  callback(err, res)
              });
          } else {
              instance.post(this.learningURL() + 'v2/content', requestObj, this.requestHeaders, function(err, res) {
                  callback(err, res)
              });
          }
      }
  }));
  
  /**
   * Service to get meta information from platform
   * 
   * @class org.ekstep.services.metaService
   * @author Santhosh Vasabhaktula <santhosh@ilimi.in>
   */
  org.ekstep.services.metaService = new(org.ekstep.services.iService.extend({
      /** 
       * @member {string} learningURL
       * @memberof org.ekstep.services.metaService
       */
      learningURL: function() {
          return this.getBaseURL() + this.getAPISlug() + '/learning/'
      },
      /** 
       * @member {string} configURL
       * @memberof org.ekstep.services.metaService
       */
      configURL: function() {
          return this.getBaseURL() + this.getAPISlug() + '/config/'
      },
      /** 
       * @member {object} requestHeaders
       * @memberof org.ekstep.services.metaService
       */
      requestHeaders: {
          "headers": {
              "content-type": "application/json",
              "user-id": "content-editor"
          }
      },
      /**
       * Returns the schema of the specified object. The schema will contain all the properties details (name, code, datatype, identifier etc,.).
       * @param  {string}   objectType  eg.AssessmentItem, Language etc.
       * @param  {Function} callback    returns error and response as arguments
       * @memberof org.ekstep.services.metaService
       */
      getDefinitions: function(objectType, callback) {
          this.getFromService(this.learningURL() + 'taxonomy/domain/definition/'+ objectType, this.requestHeaders, callback);
      },
      /**
       * Returns all property values in the specified language.
       * @param  {string}   languageCode  eg. en, hi etc.
       * @param  {Function} callback      returns error and response as arguments
       * @memberof org.ekstep.services.metaService
       */
      getResourceBundles: function(languageCode ,callback) {
          this.getFromService(this.configURL() + 'v2/config/resourcebundles/'+ languageCode, this.requestHeaders, callback);
      }
  }));
  
  /**
   * 
   * Language service helps to get languages and wordnet data.
   * @class org.ekstep.services.languageService
   * @author Santhosh Vasabhaktula <santhosh@ilimi.in>
   * 
   */
  org.ekstep.services.languageService = new(org.ekstep.services.iService.extend({
      /** 
       * @member {string} learningURL
       * @memberof org.ekstep.services.languageService
       */
      learningURL: function() {
          return this.getBaseURL() + this.getAPISlug() + '/learning/'
      },
      /** 
       * @member {string} languageURL
       * @memberof org.ekstep.services.languageService
       */
      languageURL: function() { 
          return this.getBaseURL() + this.getAPISlug() + '/language/'
      },
      /** 
       * @member {object} requestHeaders
       * @memberof org.ekstep.services.languageService
       */
      requestHeaders: {
          "headers": {
              "content-type": "application/json",
              "user-id": "content-editor"
          }
      },
      /** 
       * @member {object} wordHeaders
       * @memberof org.ekstep.services.languageService
       */
      wordHeaders: {
          "headers": {
              "Content-Type": "application/json",
              "Authorization": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiI5OGNlN2RmNmNkOTk0YWQ5YjZlYTRjNDJlNmVjYjY5MCJ9.rtr4188EwDYZywtP7S9uuv1LsivoucFxOvJFDCWvq0Y"
          }
      },
      /**
       * Get all list of languages
       * @param  {Function} callback returns error and response as arguments
       * @memberof org.ekstep.services.languageService
       */
      getLanguages: function(callback) {
          this.getFromService(this.learningURL() + 'v1/language', this.requestHeaders, callback);
      },
      /**
       * Get all list of vowel available in selected language
       * @param  {string}   language eg. en, hi etc.
       * @param  {Function} callback returns error and response as arguments
       * @memberof org.ekstep.services.languageService
       */
      getVowel: function(language, callback) {
          this.getFromService(this.languageURL() + 'v1/language/dictionary/varna/Vowel/list/' + language, this.requestHeaders, callback);
      },
      /**
       * Get all list of consonant available in selected language
       * @param  {string}   language eg. en, hi etc.
       * @param  {Function} callback returns error and response as arguments
       * @memberof org.ekstep.services.languageService
       */
      getConsonant: function(language, callback) {
          this.getFromService(this.languageURL() + 'v1/language/dictionary/varna/Consonant/list/' + language, this.requestHeaders, callback);
      },
      /**
       * Get all avalible words in given content
       * @param  {object}   data     request object contains filters, objectType, exists etc
       * @param  {Function} callback returns error and response as arguments
       * @memberof org.ekstep.services.languageService
       */
      getWords: function(data, callback) {
          this.postFromService(this.languageURL() + 'v2/language/search', data, this.wordHeaders, callback);
      },
      /**
       * Get types of word. eg. Nouns, verbs etc 
       * @param  {Function} callback returns error and response as arguments
       * @memberof org.ekstep.services.languageService
       */
      getWordDefinition: function(callback) {
          this.getFromService(this.learningURL() + 'taxonomy/en/definition/Word', this.requestHeaders, callback);
      },
      /**
       * Get all avalible keywords in given content
       * @param  {object}   data     request object
       * @param  {Function} callback returns error and response as arguments
       * @memberof org.ekstep.services.languageService
       */
      getKeyWords: function(data, callback) {
          this.postFromService(this.languageURL() + 'v1/language/parser', data, this.requestHeaders, callback);
      },
      /**
      * Transliterates english text to specified language and invokes a callback
      * @param {Object} data - object containing english text and array of languages
      * @param {Function} callback - Callback when api call returns
      * @return {void}
      */
      getTransliteration: function(data, callback) {
          this.getFromService(this.getBaseURL() + "/api/language/v2/language/transliteration/"+ data.text + "?languages=" + data.languages.toString(), this.requestHeaders, callback);
      },
      /**
       * Translates word in provided languages
       * @param {Object} req contain requested data
       * @param {Function} callback, callback function
       */
      getTranslation:  function(data, callback) {
          this.getFromService(this.getBaseURL() + "/api/language/v2/language/translations/"+ data.wordLang + '/' + data.word + '?languages=' + data.languages, this.requestHeaders, callback);
      }
  }));
  
  /**
   * Search service provides capability to search content(activities, question etc.) from  composite search API.
   * 
   * @class org.ekstep.services.searchService
   * @author Santhosh Vasabhaktula <santhosh@ilimi.in>
   */
  org.ekstep.services.searchService = new(org.ekstep.services.iService.extend({
      /** 
       * @member {string} searchURL
       * @memberof org.ekstep.services.searchService
       */
      searchURL: function() {
          return this.getBaseURL() + this.getAPISlug() + '/search/'
      },
      /** 
       * @member {object} requestHeaders
       * @memberof org.ekstep.services.searchService
       */
      requestHeaders: {
          "headers": {
              "content-type": "application/json",
              "user-id": "content-editor"
          }
      },
      initService: function() {},
      /**
       * Search method helps to get the content from search API
       * @param  {object}   request  request object will take all request parameters of search API
       * @param  {Function} callback returns error and response as arguments
       * @memberof org.ekstep.services.searchService
       */
      search: function(request, callback) {
          this.postFromService(this.searchURL() + 'v2/search', request, this.requestHeaders, callback);
      }
  }));
  
  /**
   * @author Harish kumar Gangula<harishg@ilimi.in>
   */
  org.ekstep.pluginframework.iRepo = Class.extend({
      discoverManifest: function(pluginId, pluginVer, callback) {
        callback(undefined, undefined);
      },
      resolveResource: function(pluginId, pluginVer, resource) {}
  });
  
  /**
   * @author Harish kumar Gangula<harishg@ilimi.in>
   */
  org.ekstep.pluginframework.publishedRepo = new(org.ekstep.pluginframework.iRepo.extend({
      id: "published",
      discoverManifest: function(pluginId, pluginVer, callback, publishedTime) {
          var instance = this;
          org.ekstep.pluginframework.resourceManager.loadResource(this.resolveResource(pluginId, pluginVer, "manifest.json"), "json", function(err, response) {
              callback(undefined, { "manifest": response, "repo": instance });
          }, publishedTime);
      },
      resolveResource: function(id, ver, resource) {
        return org.ekstep.pluginframework.config.pluginRepo + "/" + id + "-" + ver + "/" + resource;
      }
  }));
  org.ekstep.pluginframework.resourceManager.addRepo(org.ekstep.pluginframework.publishedRepo);