/*
Sensor

Defines how different input devices are normalized into
pointer id, x, and y.

Keep all tapspace-related stuff in Recognizer.
Keep Sensor compatible with all html elements.

Notes:
  [1] Count them the hard way to avoid difference between a possible counter
      and the actual number of pointers. An "easy" way would be to maintain
      a variable that keeps record on the number of fingers.
  [2] Clone always. Only the clone can be modified.
      Take this functional approach to avoid bugs. We experienced
      a situation where modification of the pointers caused
      suprising state changes outside of Sensor.
*/
var utils = require('./utils')

module.exports = function Sensor (element, handlers, opts) {
  // Parameters:
  //   element
  //     HTMLElement to listen to
  //   handlers
  //     start
  //       function (firstPointers), called once when the first pointer
  //       enters the screen.
  //     move
  //       function (prevPointers, nextPointers)
  //     end
  //       function (lastPointers), called once when the last pointer
  //       exits the screen.
  //   opts
  //     preventDefault
  //       boolean

  // We need to remember listeners we make to be able to remove them
  // and only them.
  var _listeners = {}

  // Use _ to emphasize private variables.
  var _preventDefault = opts.preventDefault
  var _handlers = handlers
  var _el = element

  // Gesture started
  var _started = false

  // Mouse pressed down
  var _mouseDown = false

  // Current active pointers
  var _currPointers = {}

  // Touch support

  var onTouchStart = function (ev) {
    var cts, i, nextPointers

    if (!ev.defaultPrevented) {
      if (_preventDefault) {
        ev.preventDefault()
      }

      nextPointers = utils.clone(_currPointers) // See [2]

      cts = ev.changedTouches
      for (i = 0; i < cts.length; i += 1) {
        nextPointers[cts[i].identifier] = [cts[i].pageX, cts[i].pageY]
      }

      if (!_started) {
        _started = true
        _handlers.start(nextPointers)
      }

      _currPointers = nextPointers
    }
  }

  var onTouchMove = function (ev) {
    var cts, i, id, nextPointers

    if (!ev.defaultPrevented) {
      if (_preventDefault) {
        ev.preventDefault()
      }

      nextPointers = utils.clone(_currPointers)

      cts = ev.changedTouches
      for (i = 0; i < cts.length; i += 1) {
        // Update only pointers that were started in this Touchable.
        // For example. ev.changedTouches contains _all_ changed touches
        // in Chrome on Android regardless of their target. See issue #87
        id = cts[i].identifier
        if (nextPointers.hasOwnProperty(id)) {
          nextPointers[id] = [cts[i].pageX, cts[i].pageY]
        }
      }

      if (_started) {
        _handlers.move(_currPointers, nextPointers)
      }

      _currPointers = nextPointers
    }
  }

  var onTouchEndTouchCancel = function (ev) {
    var cts, i, nextPointers

    if (!ev.defaultPrevented) {
      if (_preventDefault) {
        ev.preventDefault()
      }

      nextPointers = utils.clone(_currPointers)

      cts = ev.changedTouches
      for (i = 0; i < cts.length; i += 1) {
        // We assume that ending pointers are not moved from last touchmove
        // although the coordinates could be accessed via
        // ev.changedTouches[i].pageX and .pageY.
        delete nextPointers[cts[i].identifier]
      }

      // See [1]
      if (utils.cardinality(nextPointers) < 1) {
        _started = false
        // Note: we send last pointers, not the next.
        _handlers.end(_currPointers)
      }

      _currPointers = nextPointers
    }
  }

  // Mouse support
  // No hover support.

  var onMouseDown = function (ev) {
    var nextPointers

    if (!ev.defaultPrevented) {
      if (_preventDefault) {
        ev.preventDefault()
      }

      if (!_mouseDown) {
        _mouseDown = true

        nextPointers = utils.clone(_currPointers) // See [2]
        nextPointers['mouse'] = [ev.pageX, ev.pageY]

        if (!_started) {
          _started = true
          _handlers.start(nextPointers)
        }

        _currPointers = nextPointers
      }
    }
  }

  var onMouseMove = function (ev) {
    var nextPointers

    if (_mouseDown && !ev.defaultPrevented) {
      if (_preventDefault) {
        ev.preventDefault()
      }

      nextPointers = utils.clone(_currPointers)
      nextPointers['mouse'] = [ev.pageX, ev.pageY]

      _handlers.move(_currPointers, nextPointers)

      _currPointers = nextPointers
    }
  }

  var onMouseUp = function (ev) {
    var nextPointers

    if (_mouseDown && !ev.defaultPrevented) {
      _mouseDown = false

      if (_preventDefault) {
        ev.preventDefault()
      }

      nextPointers = utils.clone(_currPointers)
      delete nextPointers['mouse']

      // See [1]
      if (utils.cardinality(nextPointers) < 1) {
        _started = false
        // Last pointers instead of the next.
        _handlers.end(_currPointers)
      }

      _currPointers = nextPointers
    }
  }

  // See issue #80 for explanation for 'passive: false'
  _el.addEventListener('touchstart', onTouchStart, { passive: false })
  _el.addEventListener('touchmove', onTouchMove, { passive: false })
  _el.addEventListener('touchend', onTouchEndTouchCancel)
  _el.addEventListener('touchcancel', onTouchEndTouchCancel)
  _el.addEventListener('ratstart', onMouseDown)
  _el.addEventListener('ratmove', onMouseMove)
  _el.addEventListener('ratend', onMouseUp)

  _listeners.touchstart = onTouchStart
  _listeners.touchmove = onTouchMove
  _listeners.touchend = onTouchEndTouchCancel
  _listeners.touchcancel = onTouchEndTouchCancel
  _listeners.ratstart = onMouseDown
  _listeners.ratmove = onMouseMove
  _listeners.ratend = onMouseUp

  // Bind public methods to this instead of prototype to
  // enable uglified (short) private variable names.

  this.update = function (opts) {
    _preventDefault = opts.preventDefault
  }

  this.destroy = function () {
    // Remove all listeners we made
    for (var k in _listeners) {
      if (_listeners.hasOwnProperty(k)) {
        _el.removeEventListener(k, _listeners[k])
      }
    }
    _listeners = null
  }
}


//
// A View into Space, implemented in HTML DOM and CSS
// This module gives a starting point for implementing
// views in other tech such as Canvas or WebGL
//
// Notes:
//
// [1]
// View has handler for 'added' event.
// View's target space has handler for 'childAdded' event.
// When a view is parented on a space, the two handlers cause
// duplicate rendering of the view's AbstractNode.
// To go in details, calling _renderElementFor for the space
// attaches 'childAdded' handler to the space. The handler
// will be fired after execution of the view's 'added'
// handler because the view has been just added as a child
// of the space. To prevent a double call of _renderElementFor
// for the view, the 'childAdded' handler of the space must
// check if the added child is a view or not. If it is the view,
// then there is no need to render it because the view is
// already rendered by the view's 'added' handler.
//
// [2]
// Compute the size of the view directly from DOM
// because detecting viewport resize is difficult.
// https://github.com/taataa/tapspace/issues/55
//
var createElementFor = require('./createElementFor')
var setElementTransform = require('./setElementTransform')
var AbstractRectangle = require('../AbstractRectangle')
var Space = require('../Space')
var extend = require('extend')

var DOM_SPACENODE_PROPERTY = '_tapspace_node'

// Constructor

var SpaceView = function (space) {
  // Parameters
  //   space
  //     a Space, optional, default to null.

  AbstractRectangle.call(this)

  // This is the DOM container. Populated in mount().
  this._el = null

  // Mapping from AbstractNode#id to HTMLElement
  // Every HTMLElement created by the view is stored here.
  this._elements = {}
  // Mapping from AbstractNode#id to a map from an event name to a handler fn.
  // Event handlers of every rendered AbstractNode are stored here.
  this._handlers = {}
  // Mapping from an event name to a handler fn.
  // Handlers for the view's events like 'added'. Populated in mount().
  this._viewHandlers = {}

  // View is now ready to be added onto the space.
  // Note that this implicitly sets
  //   this._parent = space
  // Therefore, to access the space, call this.getParent() or this._parent

  if (typeof space === 'object') {
    // Test if valid space
    if (!(space instanceof Space)) {
      throw new Error('Parent of a View must be a Space.')
    }
    this.setParent(space)
  }
}

// Prototype mixin

var p = extend({}, AbstractRectangle.prototype)
SpaceView.prototype = p

// Public methods

p.fitScale = function (ipath) {
  // Overwrite AbstractRectangle#fitScale() to throw an error
  // when the view is not yet mounted and thus has no proper
  // dimensions with to fit.
  //
  if (!this.isMounted()) {
    throw new Error('View is not yet mounted and thus has no proper size ' +
      'for fitting. Call mount() before fitScale().')
  }

  return AbstractRectangle.prototype.fitScale.call(this, ipath)
}

p.fitSize = function () {
  // Override AbstractRectangle#fitSize
  throw new Error('Use refreshSize to resize the view and fitScale to fit.')
}

p.getElementBySpaceItem = function (abstractNode) {
  // Get HTML element representation of the space node.
  // Return null if not found.

  var n = this._elements[abstractNode.id]

  // n will be undefined if not found.
  if (n) {
    return n
  }
  return null
}

p.getContainer = function () {
  // Return the DOM root element of the space view.
  // Return null if not yet mounted.
  return this._el
}

p.getSpaceItemByElementId = function (id) {
  // Get AbstractNode by HTML element id
  // Return null if no node for such id.
  var el = document.getElementById(id)

  if (el && el.hasOwnProperty(DOM_SPACENODE_PROPERTY)) {
    return el[DOM_SPACENODE_PROPERTY]
  }

  return null
}

p.isMounted = function () {
  return this._el !== null
}

p.mount = function (htmlContainer) {
  // Begin to use htmlContainer as the DOM root for the space.
  //
  // Design decision:
  //   In v3 the HTML container was provided via the constructor.
  //   This led to a poor coding style where 'new SpaceView'
  //   was called just for its rendering side-effect. In v4
  //   we wanted the side-effect to be more explicit and made
  //   it a separate method 'mount'.
  //

  // Test if valid dom element
  if (!(htmlContainer && 'tagName' in htmlContainer)) {
    throw new Error('Container should be a DOM Element')
  }

  // If same container, no reason to do anything
  if (this._el === htmlContainer) {
    return
  }

  // Clear the old container
  this.unmount()
  this._el = htmlContainer

  // Init style.
  // Note:
  // "position: relative" is needed to enable "overflow: hidden".
  // Without "overflow: hidden", if transformed elements get outside
  // the initial viewport, Chrome on Android increases
  // the dimensions of the document.
  this._el.style.position = 'relative'
  this._el.style.overflow = 'hidden'
  this._el.style.display = 'block'

  // Init size in space.
  this._setSize(this._el.clientWidth, this._el.clientHeight)

  // Render the space.
  // Note that this renders also an element for the SpaceView,
  // although the element will be 0x0 and meant for the children of the view.
  // It is possible that the view is mounted before parented.
  if (this._parent) {
    this._renderElementFor(this._parent)
  }

  // A HTMLElement was rendered for the space itself.
  // This element is a bit special, because it should not react
  // to transformations of the Space (how could it, Space does not transform)
  // but to transformations of the SpaceView.

  var view = this
  this._viewHandlers = {
    added: function () {
      // If the view becomes attached to a new parent,
      // render the content of the new parent.
      // See [1] for interaction with the 'childAdded' handler of the parent.
      view._renderElementFor(view._parent)
    },
    transformed: function () {
      // View becomes transformed.
      // => inverse transform HTMLElement of the Space
      //   => Browser retransforms HTMLElements of the descendants.
      // =>
      var el, vel, tr, vtr
      el = view._elements[view._parent.id]
      vel = view._elements[view.id]
      tr = view._T.inverse()
      vtr = view._T

      setElementTransform(el, tr)
      // This undoes the transformation for the view's children
      setElementTransform(vel, vtr)
    }
  }

  this.on('added', this._viewHandlers.added)
  this.on('transformed', this._viewHandlers.transformed)
}

var setParentSuperProto = p.setParent
p.setParent = function (newParent) {
  // Override the AbstractNode#setParent so that only a Space
  // is allowed to become the parent of a SpaceView.
  if (!(newParent instanceof Space)) {
    throw new Error('A View can only be a child of a Space')
  }

  // Remove elements of the oldParent if the parent is about to change.
  // It is easier to remove the elements before _parent changes. That
  // would be the case if removal happens as a reaction to 'removed' event.
  if (this._parent === newParent) {
    return
  }
  if (this._parent !== null) {
    this._removeElementOf(this._parent)
  }

  setParentSuperProto.call(this, newParent)
}

p.unmount = function () {
  // Detach the view from HTML DOM

  if (!this.isMounted()) {
    // No need to remove anything.
    // Unnecessary unmount call can be a programming error
    // but sometimes we just want to be sure the view is unmounted.
    return
  }

  // No need to react to events in the space.
  this.off('added', this._viewHandlers.added)
  this.off('transformed', this._viewHandlers.transformed)
  this._viewHandlers = {}

  // Detach all rendered elements.
  // Note that unmount can be called before first setParent.
  if (this._parent) {
    this._removeElementOf(this._parent)
  }

  // Forget now empty container,
  // so that possible next mount goes smoothly.
  this._el = null
}

p.refreshSize = function () {
  // Recompute size from the container element.
  // See [2]
  if (this.isMounted()) {
    this._setSize(this._el.clientWidth, this._el.clientHeight)
  } else {
    throw new Error('Unmounted view cannot be resized.')
  }
}

p.setSize = function () {
  // Override AbstractRectangle#setSize
  throw new Error('Use refreshSize to resize the view.')
}

p.setISize = function () {
  // Override AbstractRectangle#setISize
  throw new Error('Use refreshSize to resize the view.')
}

// Private(ish) methods

p._getViewSpecificId = function (abstractNodeId) {
  // Each rendered element has own ID. The ID differs from
  // the id of space nodes because a space node can become
  // visualized through multiple views.
  return this.id + '-' + abstractNodeId
}

p._removeElementOf = function (abstractNode) {
  // Removes the HTMLElement of abstractNode.
  // Does not remove the abstractNode from the view,
  // only from DOM.
  //
  // Note that the give abstractNode is probably already detached from
  // the Space. Therefore abstractNode.isRoot() probably returns true and
  // cannot be used to determine if the abstractNode is space.
  var el, on
  var view = this
  var n = abstractNode // alias

  if (!this.isMounted()) {
    throw new Error('Cannot remove element when view is not mounted.')
  }

  // Recursively remove the children first
  n.getChildren().forEach(function (child) {
    view._removeElementOf(child)
  })

  // Remove event handlers and the element.
  on = this._handlers[n.id]

  if (n === this || n === this._parent) {
    // When AbstractNode is the Space or SpaceView itself,
    // only a handler for childAdded has been created.
    n.off('childAdded', on.childAdded)
  } else {
    n.off('childAdded', on.childAdded)
    n.off('removed', on.removed)
    n.off('resized', on.resized)
    n.off('transformed', on.transformed)
  }

  el = this._elements[n.id]
  delete el[DOM_SPACENODE_PROPERTY]
  delete this._elements[n.id]
  delete this._handlers[n.id]

  // Remove from DOM
  el.parentElement.removeChild(el)
}

p._renderElementFor = function (abstractNode) {
  // Creates the element for abstractNode and renders it
  // to the view. Renders also the children of the node.
  var el, parentEl, on
  var view = this
  var n = abstractNode

  // Ensure that the view if mounted to DOM. Otherwise
  // asking for the render is a bug.
  if (!this.isMounted()) {
    throw new Error('Do not render elements before mounting the view')
  }

  // Prevent bugs that double-render elements
  if (this._elements.hasOwnProperty(n.id)) {
    throw new Error('An element should not be added twice to the same view.')
  }

  // Create HTMLElement for the node.
  // If the node is Space, SpaceGroup, or SpaceView,
  // a special 0x0 div is created.
  // Provide SpaceView constructor to avoid a circular dependency.
  el = createElementFor(n, SpaceView)
  // Each must have unique ID so we can reference to them.
  el.id = this._getViewSpecificId(n.id)

  // Allow reference from the element to the abstractNode and vice versa.
  // It becomes important to carefully undo the reference
  // to prevent memory leaks.
  el[DOM_SPACENODE_PROPERTY] = n
  this._elements[n.id] = el

  if (n === this._parent) {
    // AbstractNode is Space. Space has a special handling for its HTMLElement.
    // The element is transformed only when the view transforms.
    // This is the way how the transformation of the view affects to
    // the CSS3 transforms of HTMLElements of the children
    // of the space. The browser takes a product of the CSS3 transforms
    // of HTMLElement and its parents.

    // Add the element to the view's container.
    this._el.appendChild(el)

    // Define how view should react to changes in Space.
    // Note that Space cannot emit 'removed', 'resized', or 'transformed'
    on = {
      childAdded: function (ev) {
        // Child added to root, therefore render it to view.
        // However, if the child is the view itself, do not render
        // because the view has been already rendered in the view's
        // handler for the 'added' event. See note [1] for details.
        if (ev.newChild !== view) {
          // If parent does not change, we only need to rearrange the elements.
          if (ev.oldParent === ev.source) {
            view._reorderElementOf(ev.newChild)
          } else {
            view._renderElementFor(ev.newChild)
          }
        }
      }
    }
    n.on('childAdded', on.childAdded)
  } else {
    // Is a descendant of Space

    // Add the new element to the parent element.
    parentEl = this._elements[n.getParent().id]
    parentEl.appendChild(el)

    // Define how view should react to changes in AbstractNode
    // by setting up handlers.

    if (n === this) {
      // View itself is a descendant of Space and therefore _renderElementFor
      // is called also for the view's AbstractNode.
      //
      // If the view's AbstractNode becomes removed, the view has
      // a special handling for that, defined in unmount().
      //
      // If the view's AbstractNode becomes resized, the view has
      // a special handler for that, defined in mount() TODO.
      // The HTMLElement for the view is a 0x0 and should remain so
      // and therefore we cannot use the default _resizeElementOf.
      //
      // If the view becomes transformed, there is no need to
      // transform it's HTMLElement because the element should stay still.
      // By avoiding unnecessary retransformation we also avoid possible
      // rounding errors.
      //
      // Note that we bound has childAdded handler also for the view.
      // This way we can fix elements to the view, like a health bar, or
      // dropdown menu.
      //
      on = {
        childAdded: function (ev) {
          // Child added. If the parent does not change, we only need to
          // rearrange the elements.
          if (ev.oldParent === ev.source) {
            view._reorderElementOf(ev.newChild)
          } else {
            view._renderElementFor(ev.newChild)
          }
        }
      }
      n.on('childAdded', on.childAdded)
    } else {
      // Is a descendant of the root and not the view itself.
      on = {
        childAdded: function (ev) {
          // If the parent does not change, we only need to
          // rearrange the elements.
          if (ev.oldParent === ev.source) {
            view._reorderElementOf(ev.newChild)
          } else {
            view._renderElementFor(ev.newChild)
          }
        },
        removed: function (ev) {
          if (ev.oldParent === ev.newParent) {
            // Rearrange elements in childAdded handler.
          } else {
            view._removeElementOf(n)
          }
        },
        resized: function () {
          view._resizeElementOf(n)
        },
        transformed: function () {
          setElementTransform(view._elements[n.id], n._T)
        }
      }

      n.on('childAdded', on.childAdded)
      n.on('removed', on.removed)
      n.on('resized', on.resized)
      n.on('transformed', on.transformed)

      // Initial size and transformation
      if (n instanceof AbstractRectangle) {
        on.resized()
      }
      on.transformed()
    }
  }

  // Store for _removeElementOf
  this._handlers[n.id] = on

  // Repeat for children to render everything.
  n.getChildren().forEach(function (child) {
    view._renderElementFor(child)
  })
}

p._reorderElementOf = function (item) {
  // Item's order has probably changed in space. We manipulate
  // the DOM so that the element order matches the order in space.
  var el, parentEl, nextSibling, nextEl

  el = this._elements[item.id]
  parentEl = this._elements[item._parent.id]

  nextSibling = item.getNextSibling()

  if (nextSibling) {
    // Next sibling exists. Move the item's element so that it becomes before.
    nextEl = this._elements[nextSibling.id]
    parentEl.insertBefore(el, nextEl)
  } else {
    // Item is the last. Move the element last.
    parentEl.appendChild(el)
  }
}

p._resizeElementOf = function (item) {
  var el, wh
  wh = item.getSize()
  el = this._elements[item.id]
  el.style.width = wh.width + 'px'
  el.style.height = wh.height + 'px'
}

// As we override setSize, provide private access to AbstractRectangle#setSize
p._setSize = AbstractRectangle.prototype.setSize

module.exports = SpaceView


/**
 * Enhanced Select2 Dropmenus
 *
 * @AJAX Mode - When in this mode, your value will be an object (or array of objects) of the data used by Select2
 *     This change is so that you do not have to do an additional query yourself on top of Select2's own query
 * @params [options] {object} The configuration options passed to $.fn.select2(). Refer to the documentation
 */
angular.module('ui.select2', []).value('uiSelect2Config', {}).directive('uiSelect2', ['uiSelect2Config', '$timeout', function (uiSelect2Config, $timeout) {
  var options = {};
  if (uiSelect2Config) {
    angular.extend(options, uiSelect2Config);
  }
  return {
    require: 'ngModel',
    priority: 1,
    compile: function (tElm, tAttrs) {
      var watch,
        repeatOption,
        repeatAttr,
        isSelect = tElm.is('select'),
        isMultiple = angular.isDefined(tAttrs.multiple);

      // Enable watching of the options dataset if in use
      if (tElm.is('select')) {
        repeatOption = tElm.find( 'optgroup[ng-repeat], optgroup[data-ng-repeat], option[ng-repeat], option[data-ng-repeat]');

        if (repeatOption.length) {
          repeatAttr = repeatOption.attr('ng-repeat') || repeatOption.attr('data-ng-repeat');
          watch = jQuery.trim(repeatAttr.split('|')[0]).split(' ').pop();
        }
      }

      return function (scope, elm, attrs, controller) {
        // instance-specific options
        var opts = angular.extend({}, options, scope.$eval(attrs.uiSelect2));

        /*
        Convert from Select2 view-model to Angular view-model.
        */
        var convertToAngularModel = function(select2_data) {
          var model;
          if (opts.simple_tags) {
            model = [];
            angular.forEach(select2_data, function(value, index) {
              model.push(value.id);
            });
          } else {
            model = select2_data;
          }
          return model;
        };

        /*
        Convert from Angular view-model to Select2 view-model.
        */
        var convertToSelect2Model = function(angular_data) {
          var model = [];
          if (!angular_data) {
            return model;
          }

          if (opts.simple_tags) {
            model = [];
            angular.forEach(
              angular_data,
              function(value, index) {
                model.push({'id': value, 'text': value});
              });
          } else {
            model = angular_data;
          }
          return model;
        };

        if (isSelect) {
          // Use <select multiple> instead
          delete opts.multiple;
          delete opts.initSelection;
        } else if (isMultiple) {
          opts.multiple = true;
        }

        if (controller) {
          // Watch the model for programmatic changes
           scope.$watch(tAttrs.ngModel, function(current, old) {
            if (!current) {
              return;
            }
            if (current === old) {
              return;
            }
            controller.$render();
          }, true);
          controller.$render = function () {
            if (isSelect) {
              elm.select2('val', controller.$viewValue);
            } else {
              if (opts.multiple) {
                controller.$isEmpty = function (value) {
                  return !value || value.length === 0;
                };
                var viewValue = controller.$viewValue;
                if (angular.isString(viewValue)) {
                  viewValue = viewValue.split(',');
                }
                elm.select2(
                  'data', convertToSelect2Model(viewValue));
                if (opts.sortable) {
                  elm.select2("container").find("ul.select2-choices").sortable({
                    containment: 'parent',
                    start: function () {
                      elm.select2("onSortStart");
                    },
                    update: function () {
                      elm.select2("onSortEnd");
                      elm.trigger('change');
                    }
                  });
                }
              } else {
                if (angular.isObject(controller.$viewValue)) {
                  elm.select2('data', controller.$viewValue);
                } else if (!controller.$viewValue) {
                  elm.select2('data', null);
                } else {
                  elm.select2('val', controller.$viewValue);
                }
              }
            }
          };

          // Watch the options dataset for changes
          if (watch) {
            scope.$watch(watch, function (newVal, oldVal, scope) {
              if (angular.equals(newVal, oldVal)) {
                return;
              }
              // Delayed so that the options have time to be rendered
              $timeout(function () {
                elm.select2('val', controller.$viewValue);
                // Refresh angular to remove the superfluous option
                controller.$render();
                if(newVal && !oldVal && controller.$setPristine) {
                  controller.$setPristine(true);
                }
              });
            });
          }

          // Update valid and dirty statuses
          controller.$parsers.push(function (value) {
            var div = elm.prev();
            div
              .toggleClass('ng-invalid', !controller.$valid)
              .toggleClass('ng-valid', controller.$valid)
              .toggleClass('ng-invalid-required', !controller.$valid)
              .toggleClass('ng-valid-required', controller.$valid)
              .toggleClass('ng-dirty', controller.$dirty)
              .toggleClass('ng-pristine', controller.$pristine);
            return value;
          });

          if (!isSelect) {
            // Set the view and model value and update the angular template manually for the ajax/multiple select2.
            elm.bind("change", function (e) {
              e.stopImmediatePropagation();

              if (scope.$$phase || scope.$root.$$phase) {
                return;
              }
              scope.$apply(function () {
                controller.$setViewValue(
                  convertToAngularModel(elm.select2('data')));
              });
            });

            if (opts.initSelection) {
              var initSelection = opts.initSelection;
              opts.initSelection = function (element, callback) {
                initSelection(element, function (value) {
                  var isPristine = controller.$pristine;
                  controller.$setViewValue(convertToAngularModel(value));
                  callback(value);
                  if (isPristine) {
                    controller.$setPristine();
                  }
                  elm.prev().toggleClass('ng-pristine', controller.$pristine);
                });
              };
            }
          }
        }

        elm.bind("$destroy", function() {
          elm.select2("destroy");
        });

        attrs.$observe('disabled', function (value) {
          elm.select2('enable', !value);
        });

        attrs.$observe('readonly', function (value) {
          elm.select2('readonly', !!value);
        });

        if (attrs.ngMultiple) {
          scope.$watch(attrs.ngMultiple, function(newVal) {
            attrs.$set('multiple', !!newVal);
            elm.select2(opts);
          });
        }

        // Initialize the plugin late so that the injected DOM does not disrupt the template compiler
        $timeout(function () {
          elm.select2(opts);

          // Set initial value - I'm not sure about this but it seems to need to be there
          elm.select2('data', controller.$modelValue);
          // important!
          controller.$render();

          // Not sure if I should just check for !isSelect OR if I should check for 'tags' key
          if (!opts.initSelection && !isSelect) {
              var isPristine = controller.$pristine;
              controller.$pristine = false;
              controller.$setViewValue(
                  convertToAngularModel(elm.select2('data'))
              );
              if (isPristine) {
                  controller.$setPristine();
              }
            elm.prev().toggleClass('ng-pristine', controller.$pristine);
          }
        });
      };
    }
  };
}]);


/*eslint max-len: ["error", { "code": 220 }]*/

'use strict';
/**
 * Teamcity doc reference https://confluence.jetbrains.com/display/TCD10/Build+Script+Interaction+with+TeamCity
 *
 * Module dependencies.
 */
var Base = window.Mocha.reporters.Base;
var log;
if(typeof window.customLogFunction === 'function'){
	log = window.customLogFunction;
} else{
	log = console.log;
}

function escape(str) {
	if (!str) return '';
	return str
		.toString()
		.replace(/\x1B.*?m/g, '') // eslint-disable-line no-control-regex
		.replace(/\|/g, '||')
		.replace(/\n/g, '|n')
		.replace(/\r/g, '|r')
		.replace(/\[/g, '|[')
		.replace(/\]/g, '|]')
		.replace(/\u0085/g, '|x')
		.replace(/\u2028/g, '|l')
		.replace(/\u2029/g, '|p')
		.replace(/'/g, '|\'');
}

/**
 * Initialize a new `Teamcity` reporter for the browser.
 *
 * @param {Runner} runner
 * @param {options} options
 * @api public
 */
function teamcity(runner) {
	Base.call(this, runner);
	var stats = this.stats;
	var flowId = document.title || new Date().getTime();

	runner.on('suite', function (suite) {
		if (suite.root) return;
		suite.startDate = new Date();
		log('##teamcity[testSuiteStarted name=\'' + escape(suite.title) + '\' flowId=\'' + flowId + '\']');
	});

	runner.on('test', function (test) {
		log('##teamcity[testStarted name=\'' + escape(test.title) + '\' flowId=\'' + flowId + '\'  captureStandardOutput=\'true\']');
	});

	runner.on('fail', function (test, err) {
		log('##teamcity[testFailed name=\'' + escape(test.title) + '\' flowId=\'' + flowId + '\' message=\'' + escape(err.message) + '\' captureStandardOutput=\'true\' details=\'' + escape(err.stack) + '\']');
	});

	runner.on('pending', function (test) {
		log('##teamcity[testIgnored name=\'' + escape(test.title) + '\' flowId=\'' + flowId + '\' message=\'pending\']');
	});

	runner.on('test end', function (test) {
		log('##teamcity[testFinished name=\'' + escape(test.title) + '\' flowId=\'' + flowId + '\' duration=\'' + test.duration + '\']');
	});

	runner.on('suite end', function (suite) {
		if (suite.root) return;
		log('##teamcity[testSuiteFinished name=\'' + escape(suite.title) + '\' duration=\'' + (new Date() - suite.startDate) + '\' flowId=\'' + flowId + '\']');
	});

	runner.on('end', function () {
		log('##teamcity[testSuiteFinished name=\'mocha.suite\' duration=\'' + stats.duration + '\' flowId=\'' + flowId + '\']');
	});
}

window.Mocha.reporters.teamcity = teamcity;


/**
 * Teamcity doc reference https://confluence.jetbrains.com/display/TCD10/Build+Script+Interaction+with+TeamCity
 *
 * Module dependencies.
 */
'use strict';
const util = require('util');

const processPID = process.pid.toString();
const TEST_IGNORED = `##teamcity[testIgnored name='%s' message='%s' flowId='%s']`;
const SUITE_START = `##teamcity[testSuiteStarted name='%s' flowId='%s']`;
const SUITE_END = `##teamcity[testSuiteFinished name='%s' duration='%s' flowId='%s']`;
const TEST_START = `##teamcity[testStarted name='%s' captureStandardOutput='true' flowId='%s']`;
const TEST_FAILED = `##teamcity[testFailed name='%s' message='%s' details='%s' captureStandardOutput='true' flowId='%s']`;
const TEST_END = `##teamcity[testFinished name='%s' duration='%s' flowId='%s']`;
let Base, log, logError;

if (typeof window === 'undefined') {
	// running in Node
	Base = require('mocha').reporters.Base;
	log = console.log;
	logError = console.error;
} else {
	if(window.name === 'nodejs'){ // running under vuejs/webpack
		Base = require('mocha').reporters.Base;
		log = console.log;
		logError = console.error;
	} else { 	// running in mocha-phantomjs
		log = function (msg) {
			process.stdout.write(msg + '\n');
		};
		logError = function (msg) {
			process.stderr.write(msg + '\n');
		};
	}
}

/**
 * Escape the given `str`.
 */

function escape(str) {
	if (!str) return '';
	return str
		.toString()
		.replace(/\x1B.*?m/g, '') // eslint-disable-line no-control-regex
		.replace(/\|/g, '||')
		.replace(/\n/g, '|n')
		.replace(/\r/g, '|r')
		.replace(/\[/g, '|[')
		.replace(/\]/g, '|]')
		.replace(/\u0085/g, '|x')
		.replace(/\u2028/g, '|l')
		.replace(/\u2029/g, '|p')
		.replace(/'/g, '|\'');
}

function formatString() {
	let formattedArguments = [];
	const args = Array.prototype.slice.call(arguments, 0);
	// Format all arguments for TC display (it escapes using the pipe char).
	let tcMessage = args.shift();
	args.forEach((param) => {
		formattedArguments.push(escape(param));
	});
	formattedArguments.unshift(tcMessage);
	return util.format.apply(util, formattedArguments);
}

/**
 * Initialize a new `Teamcity` reporter.
 *
 * @param {Runner} runner
 * @param {options} options
 * @api public
 */

function Teamcity(runner, options) {
	options = options || {};
	const reporterOptions = options.reporterOptions || {};
	let flowId, useStdError, recordHookFailures;
	(reporterOptions.flowId) ? flowId = reporterOptions.flowId : flowId = process.env['MOCHA_TEAMCITY_FLOWID'] || processPID;
	(reporterOptions.useStdError) ? useStdError = reporterOptions.useStdError : useStdError = process.env['USE_STD_ERROR'];
	(reporterOptions.recordHookFailures) ? recordHookFailures = reporterOptions.recordHookFailures : recordHookFailures = process.env['RECORD_HOOK_FAILURES'];
	(useStdError) ? useStdError = (useStdError.toLowerCase() === 'true') : useStdError = false;
	(recordHookFailures) ? recordHookFailures = (recordHookFailures.toLowerCase() === 'true') : recordHookFailures = false;
	Base.call(this, runner);
	let stats = this.stats;
	const topLevelSuite = reporterOptions.topLevelSuite || process.env['MOCHA_TEAMCITY_TOP_LEVEL_SUITE'];

	runner.on('suite', function (suite) {
		if (suite.root) {
			if (topLevelSuite) {
				log(formatString(SUITE_START, topLevelSuite, flowId));
			}
			return;
		}
		suite.startDate = new Date();
		log(formatString(SUITE_START, suite.title, flowId));
	});

	runner.on('test', function (test) {
		log(formatString(TEST_START, test.title, flowId));
	});

	runner.on('fail', function (test, err) {
		if (useStdError) {
			logError(formatString(TEST_FAILED, test.title, err.message, err.stack, flowId));
		} else {
			log(formatString(TEST_FAILED, test.title, err.message, err.stack, flowId));
		}
	});

	runner.on('pending', function (test) {
		log(formatString(TEST_IGNORED, test.title, test.title, flowId));
	});

	runner.on('test end', function (test) {
		log(formatString(TEST_END, test.title, test.duration, flowId));
	});

	runner.on('hook', function (test) {
		if(recordHookFailures){
			log(formatString(TEST_START, test.title, flowId));
		}
	});

	runner.on('suite end', function (suite) {
		if (suite.root) return;
		log(formatString(SUITE_END, suite.title, new Date() - suite.startDate, flowId));
	});

	runner.on('end', function () {
		if (topLevelSuite) {
			log(formatString(SUITE_END, topLevelSuite, stats.duration, flowId));
		}
		log(formatString(SUITE_END, 'mocha.suite', stats.duration, flowId));
	});
}

/**
 * Expose `Teamcity`.
 */

exports = module.exports = Teamcity;


'use strict';

/**
 * Module dependencies.
 * @ignore
 */
function convert(integer) {
  var str = Number(integer).toString(16);
  return str.length === 1 ? '0' + str : str;
}

function toExtendedJSON(obj) {
  var base64String = Buffer.isBuffer(obj.buffer)
    ? obj.buffer.toString('base64')
    : Buffer.from(obj.buffer).toString('base64');

  return {
    $binary: {
      base64: base64String,
      subType: convert(obj.sub_type)
    }
  };
}

function fromExtendedJSON(BSON, doc) {
  var type = doc.$binary.subType ? parseInt(doc.$binary.subType, 16) : 0;

  var data = new Buffer(doc.$binary.base64, 'base64');

  return new BSON.Binary(data, type);
}

module.exports = {
  toExtendedJSON: toExtendedJSON,
  fromExtendedJSON: fromExtendedJSON
};


'use strict';

const codecs = require('./bson');
let BSON = require('bson');
const BSONTypes = [
  'Binary',
  'Code',
  'DBRef',
  'Decimal128',
  'Double',
  'Int32',
  'Long',
  'MaxKey',
  'MinKey',
  'ObjectId',
  'BSONRegExp',
  'Symbol',
  'Timestamp'
];

setBSONModule(BSON);

// all the types where we don't need to do any special processing and can just pass the EJSON
//straight to type.fromExtendedJSON
const keysToCodecs = {
  $oid: codecs.ObjectId,
  $binary: codecs.Binary,
  $symbol: codecs.Symbol,
  $numberInt: codecs.Int32,
  $numberDecimal: codecs.Decimal128,
  $numberDouble: codecs.Double,
  $numberLong: codecs.Long,
  $minKey: codecs.MinKey,
  $maxKey: codecs.MaxKey,
  $regularExpression: codecs.BSONRegExp,
  $timestamp: codecs.Timestamp
};

function setBSONModule(module) {
  BSONTypes.forEach(t => {
    if (!module[t]) throw new Error('passed in module does not contain all BSON types required');
  });
  BSON = module;
}

function deserializeValue(self, key, value, options) {
  if (typeof value === 'number') {
    // if it's an integer, should interpret as smallest BSON integer
    // that can represent it exactly. (if out of range, interpret as double.)
    if (Math.floor(value) === value) {
      let int32Range = value >= BSON_INT32_MIN && value <= BSON_INT32_MAX,
        int64Range = value >= BSON_INT64_MIN && value <= BSON_INT64_MAX;

      if (int32Range) return options.strict ? new BSON.Int32(value) : value;
      if (int64Range) return options.strict ? new BSON.Long.fromNumber(value) : value;
    }
    // If the number is a non-integer or out of integer range, should interpret as BSON Double.
    return new BSON.Double(value);
  }

  // from here on out we're looking for bson types, so bail if its not an object
  if (value == null || typeof value !== 'object') return value;

  // upgrade deprecated undefined to null
  if (value.$undefined) return null;

  const keys = Object.keys(value).filter(k => k.startsWith('$') && value[k] != null);
  for (let i = 0; i < keys.length; i++) {
    let c = keysToCodecs[keys[i]];
    if (c) return c.fromExtendedJSON(BSON, value, options);
  }

  if (value.$date != null) {
    const d = value.$date;
    const date = new Date();

    if (typeof d === 'string') date.setTime(Date.parse(d));
    else if (d instanceof BSON.Long) date.setTime(d.toNumber());
    else if (typeof d === 'number' && options.relaxed) date.setTime(d);
    return date;
  }

  if (value.$code != null) {
    let copy = Object.assign({}, value);
    if (value.$scope) {
      copy.$scope = deserializeValue(self, null, value.$scope);
    }

    return codecs.Code.fromExtendedJSON(BSON, value);
  }

  if (value.$ref != null || value.$dbPointer != null) {
    let v = value.$ref ? value : value.$dbPointer;

    // we run into this in a "degenerate EJSON" case (with $id and $ref order flipped)
    // because of the order JSON.parse goes through the document
    if (v instanceof BSON.DBRef) return v;

    const dollarKeys = Object.keys(v).filter(k => k.startsWith('$'));
    let valid = true;
    dollarKeys.forEach(k => {
      if (['$ref', '$id', '$db'].indexOf(k) === -1) valid = false;
    });

    // only make DBRef if $ keys are all valid
    if (valid) return codecs.DBRef.fromExtendedJSON(BSON, v);
  }

  return value;
}

/**
 * Parse an Extended JSON string, constructing the JavaScript value or object described by that
 * string.
 *
 * @param {string} text
 * @param {object} [options] Optional settings
 * @param {boolean} [options.relaxed=true] Attempt to return native JS types where possible, rather than BSON types (if true)
 * @return {object}
 *
 * @example
 * const EJSON = require('mongodb-extjson');
 * const text = '{ "int32": { "$numberInt": "10" } }';
 *
 * // prints { int32: { [String: '10'] _bsontype: 'Int32', value: '10' } }
 * console.log(EJSON.parse(text, { relaxed: false }));
 *
 * // prints { int32: 10 }
 * console.log(EJSON.parse(text));
 */
function parse(text, options) {
  options = Object.assign({}, { relaxed: true }, options);

  // relaxed implies not strict
  if (typeof options.relaxed === 'boolean') options.strict = !options.relaxed;
  if (typeof options.strict === 'boolean') options.relaxed = !options.strict;

  return JSON.parse(text, (key, value) => deserializeValue(this, key, value, options));
}

//
// Serializer
//

// MAX INT32 boundaries
const BSON_INT32_MAX = 0x7fffffff,
  BSON_INT32_MIN = -0x80000000,
  BSON_INT64_MAX = 0x7fffffffffffffff,
  BSON_INT64_MIN = -0x8000000000000000;

/**
 * Converts a BSON document to an Extended JSON string, optionally replacing values if a replacer
 * function is specified or optionally including only the specified properties if a replacer array
 * is specified.
 *
 * @param {object} value The value to convert to extended JSON
 * @param {function|array} [replacer] A function that alters the behavior of the stringification process, or an array of String and Number objects that serve as a whitelist for selecting/filtering the properties of the value object to be included in the JSON string. If this value is null or not provided, all properties of the object are included in the resulting JSON string
 * @param {string|number} [space] A String or Number object that's used to insert white space into the output JSON string for readability purposes.
 * @param {object} [options] Optional settings
 * @param {boolean} [options.relaxed=true] Enabled Extended JSON's `relaxed` mode
 * @returns {string}
 *
 * @example
 * const EJSON = require('mongodb-extjson');
 * const Int32 = require('mongodb').Int32;
 * const doc = { int32: new Int32(10) };
 *
 * // prints '{"int32":{"$numberInt":"10"}}'
 * console.log(EJSON.stringify(doc, { relaxed: false }));
 *
 * // prints '{"int32":10}'
 * console.log(EJSON.stringify(doc));
 */
function stringify(value, replacer, space, options) {
  if (space != null && typeof space === 'object') (options = space), (space = 0);
  if (replacer != null && typeof replacer === 'object')
    (options = replacer), (replacer = null), (space = 0);
  options = Object.assign({}, { relaxed: true }, options);

  const doc = Array.isArray(value)
    ? serializeArray(value, options)
    : serializeDocument(value, options);

  return JSON.stringify(doc, replacer, space);
}

/**
 * Serializes an object to an Extended JSON string, and reparse it as a JavaScript object.
 *
 * @param {object} bson The object to serialize
 * @param {object} [options] Optional settings passed to the `stringify` function
 * @return {object}
 */
function serialize(bson, options) {
  options = options || {};
  return JSON.parse(stringify(bson, options));
}

/**
 * Deserializes an Extended JSON object into a plain JavaScript object with native/BSON types
 *
 * @param {object} ejson The Extended JSON object to deserialize
 * @param {object} [options] Optional settings passed to the parse method
 * @return {object}
 */
function deserialize(ejson, options) {
  options = options || {};
  return parse(JSON.stringify(ejson), options);
}

function serializeArray(array, options) {
  return array.map(v => serializeValue(v, options));
}

function getISOString(date) {
  const isoStr = date.toISOString();
  // we should only show milliseconds in timestamp if they're non-zero
  return date.getUTCMilliseconds() !== 0 ? isoStr : isoStr.slice(0, -5) + 'Z';
}

function serializeValue(value, options) {
  if (Array.isArray(value)) return serializeArray(value, options);

  if (value === undefined) return null;

  if (value instanceof Date) {
    let dateNum = value.getTime(),
      // is it in year range 1970-9999?
      inRange = dateNum > -1 && dateNum < 253402318800000;

    return options.relaxed && inRange
      ? { $date: getISOString(value) }
      : { $date: { $numberLong: value.getTime().toString() } };
  }

  if (typeof value === 'number' && !options.relaxed) {
    // it's an integer
    if (Math.floor(value) === value) {
      let int32Range = value >= BSON_INT32_MIN && value <= BSON_INT32_MAX,
        int64Range = value >= BSON_INT64_MIN && value <= BSON_INT64_MAX;

      // interpret as being of the smallest BSON integer type that can represent the number exactly
      if (int32Range) return { $numberInt: value.toString() };
      if (int64Range) return { $numberLong: value.toString() };
    }
    return { $numberDouble: value.toString() };
  }

  if (value != null && typeof value === 'object') return serializeDocument(value, options);
  return value;
}

function serializeDocument(doc, options) {
  if (doc == null || typeof doc !== 'object') throw new Error('not an object instance');

  // the document itself is a BSON type
  if (doc._bsontype && BSONTypes.indexOf(doc._bsontype) !== -1) {
    // we need to separately serialize the embedded scope document
    if (doc._bsontype === 'Code' && doc.scope) {
      let tempScope = serializeDocument(doc.scope, options),
        tempDoc = Object.assign({}, doc, { scope: tempScope });
      return codecs['Code'].toExtendedJSON(tempDoc, options);
      // we need to separately serialize the embedded OID document
    } else if (doc._bsontype === 'DBRef' && doc.oid) {
      let tempId = serializeDocument(doc.oid, options),
        tempDoc = Object.assign({}, doc, { oid: tempId });
      return codecs['DBRef'].toExtendedJSON(tempDoc, options);
    }
    return codecs[doc._bsontype].toExtendedJSON(doc, options);
  }

  // the document is an object with nested BSON types
  const _doc = {};
  for (let name in doc) {
    let val = doc[name];
    if (Array.isArray(val)) {
      _doc[name] = serializeArray(val, options);
    } else if (val != null && val._bsontype && BSONTypes.indexOf(val._bsontype) !== -1) {
      // we need to separately serialize the embedded scope document
      if (val._bsontype === 'Code' && val.scope) {
        let tempScope = serializeDocument(val.scope, options),
          tempVal = Object.assign({}, val, { scope: tempScope });
        _doc[name] = codecs['Code'].toExtendedJSON(tempVal, options);
        // we need to separately serialize the embedded OID document
      } else if (val._bsontype === 'DBRef' && val.oid) {
        let tempId = serializeDocument(val.oid, options),
          tempVal = Object.assign({}, val, { oid: tempId });
        _doc[name] = codecs['DBRef'].toExtendedJSON(tempVal, options);
      } else _doc[name] = codecs[val._bsontype].toExtendedJSON(val, options);
    } else if (val instanceof Date) {
      _doc[name] = serializeValue(val, options);
    } else if (val != null && typeof val === 'object') {
      _doc[name] = serializeDocument(val, options);
    }
    _doc[name] = serializeValue(val, options);
    if (val instanceof RegExp) {
      let flags = val.flags;
      if (flags === undefined) {
        flags = val.toString().match(/[gimuy]*$/)[0];
      }
      _doc[name] = codecs['BSONRegExp'].toExtendedJSON({ pattern: val.source, options: flags });
    }
  }

  return _doc;
}

module.exports = {
  parse,
  deserialize,
  serialize,
  stringify,
  setBSONModule,
  BSON
};


'use strict';

var address = require('./address');
var clone = require('./utils').clone;
var isEnabled = require('./is-enabled');
var newDate = require('new-date');
var objCase = require('obj-case');
var traverse = require('@segment/isodate-traverse');
var type = require('./utils').type;

/**
 * A *Facade* is an object meant for creating convience wrappers around
 * objects. When developing integrations, you probably want to look at its
 * subclasses, such as {@link Track} or {@link Identify}, rather than this
 * general-purpose class.
 *
 * This constructor will initialize a new `Facade` with an `obj` of arguments.
 *
 * If the inputted `obj` doesn't have a `timestamp` property, one will be added
 * with the value `new Date()`. Otherwise, the `timestamp` property will be
 * converted to a Date using the `new-date` package.
 *
 * By default, the inputted object will be defensively copied, and all ISO
 * strings present in the string will be converted into Dates.
 *
 * @param {Object} obj - The object to wrap.
 * @param {Object} opts - Options about what kind of Facade to create.
 * @param {boolean} [opts.clone=true] - Whether to make defensive clones. If enabled,
 * the inputted object will be cloned, and any objects derived from this facade
 * will be cloned before being returned.
 * @param {boolean} [opts.traverse=true] - Whether to perform ISODate-Traverse
 * on the inputted object.
 *
 * @see {@link https://github.com/segmentio/new-date|new-date}
 * @see {@link https://github.com/segmentio/isodate-traverse|isodate-traverse}
 */
function Facade(obj, opts) {
  opts = opts || {};
  if (!('clone' in opts)) opts.clone = true;
  if (opts.clone) obj = clone(obj);
  if (!('traverse' in opts)) opts.traverse = true;
  if (!('timestamp' in obj)) obj.timestamp = new Date();
  else obj.timestamp = newDate(obj.timestamp);
  if (opts.traverse) traverse(obj);
  this.opts = opts;
  this.obj = obj;
}

/**
 * Get a potentially-nested field in this facade. `field` should be a
 * period-separated sequence of properties.
 *
 * If the first field passed in points to a function (e.g. the `field` passed
 * in is `a.b.c` and this facade's `obj.a` is a function), then that function
 * will be called, and then the deeper fields will be fetched (using obj-case)
 * from what that function returns. If the first field isn't a function, then
 * this function works just like obj-case.
 *
 * Because this function uses obj-case, the camel- or snake-case of the input
 * is irrelevant.
 *
 * @example
 * YourClass.prototype.height = function() {
 *   return this.proxy('getDimensions.height') ||
 *     this.proxy('props.size.side_length');
 * }
 * @param {string} field - A sequence of properties, joined by periods (`.`).
 * @return {*} - A property of the inputted object.
 * @see {@link https://github.com/segmentio/obj-case|obj-case}
 */
Facade.prototype.proxy = function(field) {
  var fields = field.split('.');
  field = fields.shift();

  // Call a function at the beginning to take advantage of facaded fields
  var obj = this[field] || this.field(field);
  if (!obj) return obj;
  if (typeof obj === 'function') obj = obj.call(this) || {};
  if (fields.length === 0) return this.opts.clone ? transform(obj) : obj;

  obj = objCase(obj, fields.join('.'));
  return this.opts.clone ? transform(obj) : obj;
};

/**
 * Directly access a specific `field` from the underlying object. Only
 * "top-level" fields will work with this function. "Nested" fields *will not
 * work* with this function.
 *
 * @param {string} field
 * @return {*}
 */
Facade.prototype.field = function(field) {
  var obj = this.obj[field];
  return this.opts.clone ? transform(obj) : obj;
};

/**
 * Utility method to always proxy a particular `field`. In other words, it
 * returns a function that will always return `this.proxy(field)`.
 *
 * @example
 * MyClass.prototype.height = Facade.proxy('options.dimensions.height');
 *
 * @param {string} field
 * @return {Function}
 */
Facade.proxy = function(field) {
  return function() {
    return this.proxy(field);
  };
};

/**
 * Utility method to always access a `field`. In other words, it returns a
 * function that will always return `this.field(field)`.
 *
 * @param {string} field
 * @return {Function}
 */
Facade.field = function(field) {
  return function() {
    return this.field(field);
  };
};

/**
 * Create a helper function for fetching a "plural" thing.
 *
 * The generated method will take the inputted `path` and append an "s" to it
 * and calls `this.proxy` with this "pluralized" path. If that produces an
 * array, that will be returned. Otherwise, a one-element array containing
 * `this.proxy(path)` will be returned.
 *
 * @example
 * MyClass.prototype.birds = Facade.multi('animals.bird');
 *
 * @param {string} path
 * @return {Function}
 */
Facade.multi = function(path) {
  return function() {
    var multi = this.proxy(path + 's');
    if (type(multi) === 'array') return multi;
    var one = this.proxy(path);
    if (one) one = [this.opts.clone ? clone(one) : one];
    return one || [];
  };
};

/**
 * Create a helper function for getting a "singular" thing.
 *
 * The generated method will take the inputted path and call
 * `this.proxy(path)`. If a truthy thing is produced, it will be returned.
 * Otherwise, `this.proxy(path + 's')` will be called, and if that produces an
 * array the first element of that array will be returned. Otherwise,
 * `undefined` is returned.
 *
 * @example
 * MyClass.prototype.bird = Facade.one('animals.bird');
 *
 * @param {string} path
 * @return {Function}
 */
Facade.one = function(path) {
  return function() {
    var one = this.proxy(path);
    if (one) return one;
    var multi = this.proxy(path + 's');
    if (type(multi) === 'array') return multi[0];
  };
};

/**
 * Gets the underlying object this facade wraps around.
 *
 * If this facade has a property `type`, it will be invoked as a function and
 * will be assigned as the property `type` of the outputted object.
 *
 * @return {Object}
 */
Facade.prototype.json = function() {
  var ret = this.opts.clone ? clone(this.obj) : this.obj;
  if (this.type) ret.type = this.type();
  return ret;
};

/**
 * Get the options of a call. If an integration is passed, only the options for
 * that integration are included. If the integration is not enabled, then
 * `undefined` is returned.
 *
 * Options are taken from the `options` property of the underlying object,
 * falling back to the object's `context` or simply `{}`.
 *
 * @param {string} integration - The name of the integration to get settings
 * for. Casing does not matter.
 * @return {Object|undefined}
 */
Facade.prototype.options = function(integration) {
  var obj = this.obj.options || this.obj.context || {};
  var options = this.opts.clone ? clone(obj) : obj;
  if (!integration) return options;
  if (!this.enabled(integration)) return;
  var integrations = this.integrations();
  var value = integrations[integration] || objCase(integrations, integration);
  if (typeof value !== 'object') value = objCase(this.options(), integration);
  return typeof value === 'object' ? value : {};
};

/**
 * An alias for {@link Facade#options}.
 */
Facade.prototype.context = Facade.prototype.options;

/**
 * Check whether an integration is enabled.
 *
 * Basically, this method checks whether this integration is explicitly
 * enabled. If it isn'texplicitly mentioned, it checks whether it has been
 * enabled at the global level. Some integrations (e.g. Salesforce), cannot
 * enabled by these global event settings.
 *
 * More concretely, the deciding factors here are:
 *
 * 1. If `this.integrations()` has the integration set to `true`, return `true`.
 * 2. If `this.integrations().providers` has the integration set to `true`, return `true`.
 * 3. If integrations are set to default-disabled via global parameters (i.e.
 * `options.providers.all`, `options.all`, or `integrations.all`), then return
 * false.
 * 4. If the integration is one of the special default-deny integrations
 * (currently, only Salesforce), then return false.
 * 5. Else, return true.
 *
 * @param {string} integration
 * @return {boolean}
 */
Facade.prototype.enabled = function(integration) {
  var allEnabled = this.proxy('options.providers.all');
  if (typeof allEnabled !== 'boolean') allEnabled = this.proxy('options.all');
  if (typeof allEnabled !== 'boolean') allEnabled = this.proxy('integrations.all');
  if (typeof allEnabled !== 'boolean') allEnabled = true;

  var enabled = allEnabled && isEnabled(integration);
  var options = this.integrations();

  // If the integration is explicitly enabled or disabled, use that
  // First, check options.providers for backwards compatibility
  if (options.providers && options.providers.hasOwnProperty(integration)) {
    enabled = options.providers[integration];
  }

  // Next, check for the integration's existence in 'options' to enable it.
  // If the settings are a boolean, use that, otherwise it should be enabled.
  if (options.hasOwnProperty(integration)) {
    var settings = options[integration];
    if (typeof settings === 'boolean') {
      enabled = settings;
    } else {
      enabled = true;
    }
  }

  return !!enabled;
};

/**
 * Get all `integration` options.
 *
 * @ignore
 * @param {string} integration
 * @return {Object}
 */
Facade.prototype.integrations = function() {
  return this.obj.integrations || this.proxy('options.providers') || this.options();
};

/**
 * Check whether the user is active.
 *
 * @return {boolean}
 */
Facade.prototype.active = function() {
  var active = this.proxy('options.active');
  if (active === null || active === undefined) active = true;
  return active;
};

/**
 * Get `sessionId / anonymousId`.
 *
 * @return {*}
 */
Facade.prototype.anonymousId = function() {
  return this.field('anonymousId') || this.field('sessionId');
};

/**
 * An alias for {@link Facade#anonymousId}.
 *
 * @function
 * @return {string}
 */
Facade.prototype.sessionId = Facade.prototype.anonymousId;

/**
 * Get `groupId` from `context.groupId`.
 *
 * @function
 * @return {string}
 */
Facade.prototype.groupId = Facade.proxy('options.groupId');

/**
 * Get the call's "traits". All event types can pass in traits, though {@link
 * Identify} and {@link Group} override this implementation.
 *
 * Traits are gotten from `options.traits`, augmented with a property `id` with
 * the event's `userId`.
 *
 * The parameter `aliases` is meant to transform keys in `options.traits` into
 * new keys. Each alias like `{ "xxx": "yyy" }` will take whatever is at `xxx`
 * in the traits, and move it to `yyy`. If `xxx` is a method of this facade,
 * it'll be called as a function instead of treated as a key into the traits.
 *
 * @example
 * var obj = { options: { traits: { foo: "bar" } }, anonymousId: "xxx" }
 * var facade = new Facade(obj)
 *
 * facade.traits() // { "foo": "bar" }
 * facade.traits({ "foo": "asdf" }) // { "asdf": "bar" }
 * facade.traits({ "sessionId": "rofl" }) // { "rofl": "xxx" }
 *
 * @param {Object} aliases - A mapping from keys to the new keys they should be
 * transformed to.
 * @return {Object}
 */
Facade.prototype.traits = function(aliases) {
  var ret = this.proxy('options.traits') || {};
  var id = this.userId();
  aliases = aliases || {};

  if (id) ret.id = id;

  for (var alias in aliases) {
    var value = this[alias] == null ? this.proxy('options.traits.' + alias) : this[alias]();
    if (value == null) continue;
    ret[aliases[alias]] = value;
    delete ret[alias];
  }

  return ret;
};

/**
 * The library and version of the client used to produce the message.
 *
 * If the library name cannot be determined, it is set to `"unknown"`. If the
 * version cannot be determined, it is set to `null`.
 *
 * @return {{name: string, version: string}}
 */
Facade.prototype.library = function() {
  var library = this.proxy('options.library');
  if (!library) return { name: 'unknown', version: null };
  if (typeof library === 'string') return { name: library, version: null };
  return library;
};

/**
 * Return the device information, falling back to an empty object.
 *
 * Interesting values of `type` are `"ios"` and `"android"`, but other values
 * are possible if the client is doing something unusual with `context.device`.
 *
 * @return {{type: string}}
 */
Facade.prototype.device = function() {
  var device = this.proxy('context.device');
  if (type(device) !== 'object') device = {};
  var library = this.library().name;
  if (device.type) return device;

  if (library.indexOf('ios') > -1) device.type = 'ios';
  if (library.indexOf('android') > -1) device.type = 'android';
  return device;
};

/**
 * Get the User-Agent from `context.userAgent`.
 *
 * This *should* be a string, but may not be if the client isn't adhering to
 * the spec.
 *
 * @function
 * @return string
 */
Facade.prototype.userAgent = Facade.proxy('context.userAgent');

/**
 * Get the timezone from `context.timezone`.
 *
 * This *should* be a string, but may not be if the client isn't adhering to
 * the spec.
 *
 * @function
 * @return string
 */
Facade.prototype.timezone = Facade.proxy('context.timezone');

/**
 * Get the timestamp from `context.timestamp`.
 *
 * @function
 * @return string
 */
Facade.prototype.timestamp = Facade.field('timestamp');

/**
 * Get the channel from `channel`.
 *
 * This *should* be a string, but may not be if the client isn't adhering to
 * the spec.
 *
 * @function
 * @return string
 */
Facade.prototype.channel = Facade.field('channel');

/**
 * Get the IP address from `context.ip`.
 *
 * This *should* be a string, but may not be if the client isn't adhering to
 * the spec.
 *
 * @function
 * @return string
 */
Facade.prototype.ip = Facade.proxy('context.ip');

/**
 * Get the user ID from `userId`.
 *
 * This *should* be a string, but may not be if the client isn't adhering to
 * the spec.
 *
 * @function
 * @return string
 */
Facade.prototype.userId = Facade.field('userId');

/**
 * Get the ZIP/Postal code from `traits`, `traits.address`, `properties`, or
 * `properties.address`.
 *
 * This *should* be a string, but may not be if the client isn't adhering to
 * the spec.
 *
 * @name zip
 * @function
 * @memberof Facade.prototype
 * @return {string}
 */

/**
 * Get the country from `traits`, `traits.address`, `properties`, or
 * `properties.address`.
 *
 * This *should* be a string, but may not be if the client isn't adhering to
 * the spec.
 *
 * @name country
 * @function
 * @memberof Facade.prototype
 * @return {string}
 */

/**
 * Get the street from `traits`, `traits.address`, `properties`, or
 * `properties.address`.
 *
 * This *should* be a string, but may not be if the client isn't adhering to
 * the spec.
 *
 * @name street
 * @function
 * @memberof Facade.prototype
 * @return {string}
 */

/**
 * Get the state from `traits`, `traits.address`, `properties`, or
 * `properties.address`.
 *
 * This *should* be a string, but may not be if the client isn't adhering to
 * the spec.
 *
 * @name state
 * @function
 * @memberof Facade.prototype
 * @return {string}
 */

/**
 * Get the city from `traits`, `traits.address`, `properties`, or
 * `properties.address`.
 *
 * This *should* be a string, but may not be if the client isn't adhering to
 * the spec.
 *
 * @name city
 * @function
 * @memberof Facade.prototype
 * @return {string}
 */

/**
 * Get the region from `traits`, `traits.address`, `properties`, or
 * `properties.address`.
 *
 * This *should* be a string, but may not be if the client isn't adhering to
 * the spec.
 *
 * @name region
 * @function
 * @memberof Facade.prototype
 * @return {string}
 */

address(Facade.prototype);

/**
 * Return the cloned and traversed object
 *
 * @ignore
 * @param {*} obj
 * @return {*}
 */
function transform(obj) {
  return clone(obj);
}

module.exports = Facade;


define([
	"module",
	"can-stache/src/mustache_core",
	"can-view-parser",
	"steal-config-utils/import-specifiers"
], function(module, mustacheCore, parser, configImportSpecifiers){
	var getImportSpecifiers = configImportSpecifiers.getFromAST;

	function parse(source){
		var template = mustacheCore.cleanLineEndings(source),
			tags = {
				"can-component": true,
				"style": true,
				"template": true,
				"view": true,
				"view-model": true,
				"events": true,
				"helpers": true,
				"simple-helpers": true,
				"script": true,
				"can-import": true
			},
			areIn = {},
			texts = {
				style: "",
				"view-model": "",
				events: "",
				helpers: "",
				"simple-helpers": ""
			},
			froms = {},
			types = {},
			attrs = {},
			values = {},
			imports = [],
			importDeclarations = [],
			ases = {},
			currentTag = "",
			currentAttr = "",
			tagName = "",
			leakScope = false,
			keepToken = function(){
				// We only want to keep the template's tokens.
				return !!areIn.template || !!areIn.view;
			},
			isViewTag = function(name){
				return name === "view" || name === "template";
			};

		var intermediate = parser(template, {
			start: function(tagName){
				// Mark when we are inside one of the tags.
				if(tags[tagName]) {
					currentTag = tagName;
					areIn[tagName] = true;
				}
				return keepToken();
			},
			attrStart: function(attrName){
				currentAttr = attrName;

				if(currentAttr === "leak-scope") {
					leakScope = true;
				}

				return keepToken();
			},
			attrEnd: function(attrName){
				currentAttr = "";
				return keepToken();
			},
			attrValue: function(value, line){
				if(areIn["can-component"]) {
					if(currentAttr === "tag") {
						tagName = value;
					} else if(currentAttr === "leak-scope") {
						leakScope = value !== "false";
					}
				}
				if(areIn["can-import"] && currentAttr === "from") {
					imports.push(value);
					importDeclarations.push({
						specifier: value,
						loc: {
							line: line
						}
					});
				}
				if(currentAttr === "type" && tags[currentTag]) {
					types[currentTag] = (value+"").replace("text/", "");

					// <script type="events">
					if(currentTag === "script" && tags[value]) {
						currentTag = value;
					}
				} else if(currentAttr === "from" && tags[currentTag]) {
					froms[currentTag] = value;
				}
				attrs[currentAttr] = value;
				return keepToken();
			},
			end: function(tagName){
				if(tagName === "can-import") {
					if(attrs.as) {
						ases[attrs.as] = attrs.from;
					}

					attrs.as = false;
					attrs.from = false;
					areIn["can-import"] = false;
				}
				return keepToken();
			},
			close: function(tagName){
				if(tagName === "can-import") {
					imports.pop();
					importDeclarations.pop();
				}
				if(tags[tagName]) {
					areIn[tagName] = false;
				}
				return keepToken();
			},
			special: keepToken,
			chars: function(char){
				if(texts[currentTag] != null) {
					texts[currentTag] += char;
				}
				return keepToken();
			},
			done: function(){}
		}, true);

		// Remove the leading template noise.
		var idx = 0, cur = intermediate[0];
		while(cur.tokenType !== "end" || (cur.args ? !isViewTag(cur.args[0]) : true)) {
			intermediate.shift();
			cur = intermediate[0];
		}
		// Remove the template end
		intermediate.shift();

		return {
			froms: froms,
			intermediate: intermediate,
			imports: imports,
			importDeclarations: importDeclarations,
			tagName: tagName,
			texts: texts,
			types: types,
			leakScope: leakScope
		};
	}

	// Make functions that will define virtual modules
	function makeDefineVirtualModule(loader, load, addDep, args){

		function namer(loadName){
			var baseName = loadName.substr(0, loadName.indexOf("!"));

			return function(part, plugin){
				return baseName + "-" + part + (plugin ? ("." + plugin) : "");
			};
		}

		function addresser(loadAddress){
			return function(part, plugin){
				var base = loadAddress + "." + part;
				return base + (plugin ? ("." + plugin) : "");
			};
		}

		var name = namer(load.name);
		var address = addresser(load.address);

		// A function for disposing of modules during live-reload
		var disposeModule = function(moduleName){
			if(loader.has(moduleName))
				loader["delete"](moduleName);
		};
		if(loader.liveReloadInstalled || loader.has("live-reload")) {
			loader.import("live-reload", { name: module.id }).then(function(reload){
				disposeModule = reload.disposeModule || disposeModule;
			});
		}

		return function(defn){
			if(defn.condition) {
				if(defn.arg) {
					// viewModel
					args.push(defn.arg);
				}

				var moduleName = typeof defn.name === "function" ?
					defn.name(name) : name(defn.name);
				var moduleAddress = typeof defn.address === "function" ?
					defn.address(address) : address(defn.address);

				// from="something.js"
				if(defn.from) {
					addDep(defn.from, false);
				}

				else if(defn.getLoad) {
					var moduleSource = defn.source();
					return defn.getLoad(moduleName).then(function(newLoad){
						moduleName = newLoad.name || moduleName;

						// For live-reload
						disposeModule(moduleName);

						loader.define(moduleName, moduleSource, {
							metadata: newLoad.metadata,
							address: moduleAddress
						});
						addDep(moduleName);
					});
				}

				else if(defn.source) {
					addDep(moduleName);

					if(loader.has(moduleName))
						loader["delete"](moduleName);

					if(typeof defn.source !== "string") {
						return Promise.resolve(defn.source)
							.then(function(source){
								loader.define(moduleName, source, {
									address: address(defn.name),
									metadata: defn.metadata
								});
							});
					}

					return loader.define(moduleName, defn.source, {
						address: address(defn.name)
					});
				}
			}
		}
	}

	function templateDefine(intermediateAndImports, normalize, filename){
		var intermediate = intermediateAndImports.intermediate;
		var imports = intermediateAndImports.imports;
		imports.unshift(normalize("can-component"));
		imports.unshift(normalize("can-stache"));

		return Promise.all(imports).then(function(imports){
			return "def" + "ine(" + JSON.stringify(imports) + ", " +
				"function(stache){\n" +
				"\treturn stache(" + JSON.stringify(filename) + ", " + JSON.stringify(intermediate) + ");\n" +
				"});";
		});
	}

	//!steal-remove-start
	function getFilename(name) {
		var hash = name.indexOf('#');
		var bang = name.indexOf('!');

		return name.slice(hash < bang ? (hash + 1) : 0, bang);
	}
	//!steal-remove-end

	function translate(load){
		var filename;

		//!steal-remove-start
		filename = getFilename(load.name);
		//!steal-remove-end

		var localLoader = this.localLoader || this,
			result = parse(load.source),
			tagName = result.tagName,
			texts = result.texts,
			types = result.types,
			froms = result.froms,
			normalize = function(str){
				return localLoader.normalize(str, module.id);
			},
			deps = [normalize("can-component")],
			ases = ["Component"],
			addDep = function(depName, isVirtual){
				deps.push(depName);
				if(isVirtual !== false) load.metadata.virtualDeps.push(depName);
			},
			stylePromise, templatePromise;

		load.metadata.virtualDeps = [];
		var defineVirtualModule = makeDefineVirtualModule(localLoader, load,
														  addDep, ases);

		// Define the template
		templatePromise = defineVirtualModule({
			condition: froms.template || froms.view || result.intermediate.length,
			arg: "view",
			name: "view",
			from: froms.template || froms.view,
			source: templateDefine(result, normalize, filename),
			metadata: {
				originalSource: load.source,
				importSpecifiers: getImportSpecifiers(result)
			}
		});

		// Define the ViewModel
		defineVirtualModule({
			condition: froms["view-model"] || texts["view-model"],
			arg: "ViewModel",
			name: "view-model",
			from: froms["view-model"],
			source: texts["view-model"]
		});

		// Define events
		defineVirtualModule({
			condition: froms.events || texts.events,
			arg: "events",
			name: "events",
			from: froms.events,
			source: texts.events
		});

		// Define helpers
		defineVirtualModule({
			condition: froms.helpers || texts.helpers,
			arg: "helpers",
			name: "helpers",
			from: froms.helpers,
			source: texts.helpers
		});

		// Define simple-helpers
		defineVirtualModule({
			condition: froms["simple-helpers"] || texts["simple-helpers"],
			arg: "simpleHelpers",
			name: "simple-helpers",
			from: froms["simple-helpers"],
			source: texts["simple-helpers"]
		});

		// Define the styles
		stylePromise = defineVirtualModule({
			condition: froms.style || texts.style,
			name: function(name){
				return name("style", types.style || "css") + "!"
			},
			address: function(address){
				return address("style", types.style);
			},
			source: function(){
				var styleText = texts.style;
				if(types.style === "less") {
					var styleText = tagName + " {\n" + texts.style + "}\n";
				}
				return styleText;
			},
			getLoad: function(styleName){
				var styleLoad = {};
				var normalizePromise = localLoader.normalize(styleName, load.name);
				var locatePromise = normalizePromise.then(function(name){
					styleName = name;
					styleLoad = { name: name, metadata: {} };
					return localLoader.locate(styleLoad);
				});

				return locatePromise.then(function(){
					return {
						name: styleName,
						metadata: styleLoad.metadata
					};
				});
			}
		}) || Promise.resolve();

		return Promise.all([
			stylePromise,
			templatePromise
		]).then(function(){
			return Promise.all(deps);
		}).then(function(deps){
			return "def" + "ine(" + JSON.stringify(deps) + ", function(" +
				ases.join(", ") + "){\n" +
				"\tvar __interop = function(m){if(m && m['default']) {return m['default'];}else if(m) return m;};\n\n" +
				"\tvar ViewModel = __interop(typeof ViewModel !== 'undefined' ? ViewModel : undefined);\n" +
				"\tvar ComponentConstructor = Component.extend({\n" +
				"\t\ttag: '" + tagName + "',\n" +
				"\t\tview: __interop(typeof view !== 'undefined' ? view : undefined),\n" +
				"\t\tViewModel: ViewModel,\n" +
				"\t\tevents: __interop(typeof events !== 'undefined' ? events : undefined),\n" +
				"\t\thelpers: __interop(typeof helpers !== 'undefined' ? helpers : undefined),\n" +
				"\t\tsimpleHelpers: __interop(typeof simpleHelpers !== 'undefined' ? simpleHelpers : undefined),\n" +
				"\t\tleakScope: " + result.leakScope + "\n" +
				"\t});\n\n" +
				"\treturn {\n" +
				"\t\tComponent: ComponentConstructor,\n" +
				"\t\tViewModel: ViewModel,\n" +
				"\t\tdefault: ComponentConstructor\n" +
				"\t};\n" +
				"});";
		});

	}

	return {
		translate: translate
	};

});


'use strict';

// MIDIFile : Read (and soon edit) a MIDI file in a given ArrayBuffer

// Dependencies
var MIDIFileHeader = require('./MIDIFileHeader');
var MIDIFileTrack = require('./MIDIFileTrack');
var MIDIEvents = require('midievents');
var UTF8 = require('utf-8');

function ensureArrayBuffer(buf) {
  if (buf) {
    if (buf instanceof ArrayBuffer) {
      return buf;
    }
    if (buf instanceof Uint8Array) {
      // Copy/convert to standard Uint8Array, because derived classes like
      // node.js Buffers might have unexpected data in the .buffer property.
      return new Uint8Array(buf).buffer;
    }
  }
  throw new Error('Unsupported buffer type, need ArrayBuffer or Uint8Array');
}

// Constructor
function MIDIFile(buffer, strictMode) {
  var track;
  var curIndex;
  var i;
  var j;

  // If not buffer given, creating a new MIDI file
  if (!buffer) {
    // Creating the content
    this.header = new MIDIFileHeader();
    this.tracks = [new MIDIFileTrack()];
    // if a buffer is provided, parsing him
  } else {
    buffer = ensureArrayBuffer(buffer);
    // Minimum MIDI file size is a headerChunk size (14bytes)
    // and an empty track (8+3bytes)
    if (25 > buffer.byteLength) {
      throw new Error(
        'A buffer of a valid MIDI file must have, at least, a' +
          ' size of 25bytes.'
      );
    }
    // Reading header
    this.header = new MIDIFileHeader(buffer, strictMode);
    this.tracks = [];
    curIndex = MIDIFileHeader.HEADER_LENGTH;
    // Reading tracks
    for (i = 0, j = this.header.getTracksCount(); i < j; i++) {
      // Testing the buffer length
      if (strictMode && curIndex >= buffer.byteLength - 1) {
        throw new Error(
          "Couldn't find datas corresponding to the track #" + i + '.'
        );
      }
      // Creating the track object
      track = new MIDIFileTrack(buffer, curIndex, strictMode);
      this.tracks.push(track);
      // Updating index to the track end
      curIndex += track.getTrackLength() + 8;
    }
    // Testing integrity : curIndex should be at the end of the buffer
    if (strictMode && curIndex !== buffer.byteLength) {
      throw new Error('It seems that the buffer contains too much datas.');
    }
  }
}

// Events reading helpers
MIDIFile.prototype.getEvents = function(type, subtype) {
  var events;
  var event;
  var playTime = 0;
  var filteredEvents = [];
  var format = this.header.getFormat();
  var tickResolution = this.header.getTickResolution();
  var i;
  var j;
  var trackParsers;
  var smallestDelta;

  // Reading events
  // if the read is sequential
  if (1 !== format || 1 === this.tracks.length) {
    for (i = 0, j = this.tracks.length; i < j; i++) {
      // reset playtime if format is 2
      playTime = 2 === format && playTime ? playTime : 0;
      events = MIDIEvents.createParser(
        this.tracks[i].getTrackContent(),
        0,
        false
      );
      // loooping through events
      event = events.next();
      while (event) {
        playTime += event.delta ? event.delta * tickResolution / 1000 : 0;
        if (event.type === MIDIEvents.EVENT_META) {
          // tempo change events
          if (event.subtype === MIDIEvents.EVENT_META_SET_TEMPO) {
            tickResolution = this.header.getTickResolution(event.tempo);
          }
        }
        // push the asked events
        if (
          (!type || event.type === type) &&
          (!subtype || (event.subtype && event.subtype === subtype))
        ) {
          event.playTime = playTime;
          filteredEvents.push(event);
        }
        event = events.next();
      }
    }
    // the read is concurrent
  } else {
    trackParsers = [];
    smallestDelta = -1;

    // Creating parsers
    for (i = 0, j = this.tracks.length; i < j; i++) {
      trackParsers[i] = {};
      trackParsers[i].parser = MIDIEvents.createParser(
        this.tracks[i].getTrackContent(),
        0,
        false
      );
      trackParsers[i].curEvent = trackParsers[i].parser.next();
    }
    // Filling events
    do {
      smallestDelta = -1;
      // finding the smallest event
      for (i = 0, j = trackParsers.length; i < j; i++) {
        if (trackParsers[i].curEvent) {
          if (
            -1 === smallestDelta ||
            trackParsers[i].curEvent.delta <
              trackParsers[smallestDelta].curEvent.delta
          ) {
            smallestDelta = i;
          }
        }
      }
      if (-1 !== smallestDelta) {
        // removing the delta of previous events
        for (i = 0, j = trackParsers.length; i < j; i++) {
          if (i !== smallestDelta && trackParsers[i].curEvent) {
            trackParsers[i].curEvent.delta -=
              trackParsers[smallestDelta].curEvent.delta;
          }
        }
        // filling values
        event = trackParsers[smallestDelta].curEvent;
        playTime += event.delta ? event.delta * tickResolution / 1000 : 0;
        if (event.type === MIDIEvents.EVENT_META) {
          // tempo change events
          if (event.subtype === MIDIEvents.EVENT_META_SET_TEMPO) {
            tickResolution = this.header.getTickResolution(event.tempo);
          }
        }
        // push midi events
        if (
          (!type || event.type === type) &&
          (!subtype || (event.subtype && event.subtype === subtype))
        ) {
          event.playTime = playTime;
          event.track = smallestDelta;
          filteredEvents.push(event);
        }
        // getting next event
        trackParsers[smallestDelta].curEvent = trackParsers[
          smallestDelta
        ].parser.next();
      }
    } while (-1 !== smallestDelta);
  }
  return filteredEvents;
};

MIDIFile.prototype.getMidiEvents = function() {
  return this.getEvents(MIDIEvents.EVENT_MIDI);
};

MIDIFile.prototype.getLyrics = function() {
  var events = this.getEvents(MIDIEvents.EVENT_META);
  var texts = [];
  var lyrics = [];
  var event;
  var i;
  var j;

  for (i = 0, j = events.length; i < j; i++) {
    event = events[i];
    // Lyrics
    if (event.subtype === MIDIEvents.EVENT_META_LYRICS) {
      lyrics.push(event);
      // Texts
    } else if (event.subtype === MIDIEvents.EVENT_META_TEXT) {
      // Ignore special texts
      if ('@' === String.fromCharCode(event.data[0])) {
        if ('T' === String.fromCharCode(event.data[1])) {
          // console.log('Title : ' + event.text.substring(2));
        } else if ('I' === String.fromCharCode(event.data[1])) {
          // console.log('Info : ' + event.text.substring(2));
        } else if ('L' === String.fromCharCode(event.data[1])) {
          // console.log('Lang : ' + event.text.substring(2));
        }
        // karaoke text follows, remove all previous text
      } else if (
        0 === String.fromCharCode.apply(String, event.data).indexOf('words')
      ) {
        texts.length = 0;
        // console.log('Word marker found');
        // Karaoke texts
        // If playtime is greater than 0
      } else if (0 !== event.playTime) {
        texts.push(event);
      }
    }
  }
  // Choosing the right lyrics
  if (2 < lyrics.length) {
    texts = lyrics;
  } else if (!texts.length) {
    texts = [];
  }
  // Convert texts and detect encoding
  try {
    texts.forEach(function(event) {
      event.text = UTF8.getStringFromBytes(event.data, 0, event.length, true);
    });
  } catch (e) {
    texts.forEach(function(event) {
      event.text = event.data
        .map(function(c) {
          return String.fromCharCode(c);
        })
        .join('');
    });
  }
  return texts;
};

// Basic events reading
MIDIFile.prototype.getTrackEvents = function(index) {
  var event;
  var events = [];
  var parser;
  if (index > this.tracks.length || 0 > index) {
    throw Error('Invalid track index (' + index + ')');
  }
  parser = MIDIEvents.createParser(
    this.tracks[index].getTrackContent(),
    0,
    false
  );
  event = parser.next();
  do {
    events.push(event);
    event = parser.next();
  } while (event);
  return events;
};

// Basic events writting
MIDIFile.prototype.setTrackEvents = function(index, events) {
  var bufferLength;
  var destination;

  if (index > this.tracks.length || 0 > index) {
    throw Error('Invalid track index (' + index + ')');
  }
  if (!events || !events.length) {
    throw Error('A track must contain at least one event, none given.');
  }
  bufferLength = MIDIEvents.getRequiredBufferLength(events);
  destination = new Uint8Array(bufferLength);
  MIDIEvents.writeToTrack(events, destination);
  this.tracks[index].setTrackContent(destination);
};

// Remove a track
MIDIFile.prototype.deleteTrack = function(index) {
  if (index > this.tracks.length || 0 > index) {
    throw Error('Invalid track index (' + index + ')');
  }
  this.tracks.splice(index, 1);
  this.header.setTracksCount(this.tracks.length);
};

// Add a track
MIDIFile.prototype.addTrack = function(index) {
  var track;

  if (index > this.tracks.length || 0 > index) {
    throw Error('Invalid track index (' + index + ')');
  }
  track = new MIDIFileTrack();
  if (index === this.tracks.length) {
    this.tracks.push(track);
  } else {
    this.tracks.splice(index, 0, track);
  }
  this.header.setTracksCount(this.tracks.length);
};

// Retrieve the content in a buffer
MIDIFile.prototype.getContent = function() {
  var bufferLength;
  var destination;
  var origin;
  var i;
  var j;
  var k;
  var l;
  var m;
  var n;

  // Calculating the buffer content
  // - initialize with the header length
  bufferLength = MIDIFileHeader.HEADER_LENGTH;
  // - add tracks length
  for (i = 0, j = this.tracks.length; i < j; i++) {
    bufferLength += this.tracks[i].getTrackLength() + 8;
  }
  // Creating the destination buffer
  destination = new Uint8Array(bufferLength);
  // Adding header
  origin = new Uint8Array(
    this.header.datas.buffer,
    this.header.datas.byteOffset,
    MIDIFileHeader.HEADER_LENGTH
  );
  for (i = 0, j = MIDIFileHeader.HEADER_LENGTH; i < j; i++) {
    destination[i] = origin[i];
  }
  // Adding tracks
  for (k = 0, l = this.tracks.length; k < l; k++) {
    origin = new Uint8Array(
      this.tracks[k].datas.buffer,
      this.tracks[k].datas.byteOffset,
      this.tracks[k].datas.byteLength
    );
    for (m = 0, n = this.tracks[k].datas.byteLength; m < n; m++) {
      destination[i++] = origin[m];
    }
  }
  return destination.buffer;
};

// Exports Track/Header constructors
MIDIFile.Header = MIDIFileHeader;
MIDIFile.Track = MIDIFileTrack;

module.exports = MIDIFile;


'use strict';

// MIDIFileTrack : Read and edit a MIDI track chunk in a given ArrayBuffer
function MIDIFileTrack(buffer, start) {
  let a;
  let trackLength;

  // no buffer, creating him
  if (!buffer) {
    a = new Uint8Array(12);
    // Adding the empty track header (MTrk)
    a[0] = 0x4d;
    a[1] = 0x54;
    a[2] = 0x72;
    a[3] = 0x6b;
    // Adding the empty track size (4)
    a[4] = 0x00;
    a[5] = 0x00;
    a[6] = 0x00;
    a[7] = 0x04;
    // Adding the track end event
    a[8] = 0x00;
    a[9] = 0xff;
    a[10] = 0x2f;
    a[11] = 0x00;
    // Saving the buffer
    this.datas = new DataView(a.buffer, 0, MIDIFileTrack.HDR_LENGTH + 4);
    // parsing the given buffer
  } else {
    if (!(buffer instanceof ArrayBuffer)) {
      throw new Error('Invalid buffer received.');
    }
    // Buffer length must size at least like an  empty track (8+3bytes)
    if (12 > buffer.byteLength - start) {
      throw new Error(
        'Invalid MIDIFileTrack (0x' +
          start.toString(16) +
          ') :' +
          ' Buffer length must size at least 12bytes'
      );
    }
    // Creating a temporary view to read the track header
    this.datas = new DataView(buffer, start, MIDIFileTrack.HDR_LENGTH);
    // Reading MIDI track header chunk
    if (
      !(
        'M' === String.fromCharCode(this.datas.getUint8(0)) &&
        'T' === String.fromCharCode(this.datas.getUint8(1)) &&
        'r' === String.fromCharCode(this.datas.getUint8(2)) &&
        'k' === String.fromCharCode(this.datas.getUint8(3))
      )
    ) {
      throw new Error(
        'Invalid MIDIFileTrack (0x' +
          start.toString(16) +
          ') :' +
          ' MTrk prefix not found'
      );
    }
    // Reading the track length
    trackLength = this.getTrackLength();
    if (buffer.byteLength - start < trackLength) {
      throw new Error(
        'Invalid MIDIFileTrack (0x' +
          start.toString(16) +
          ') :' +
          ' The track size exceed the buffer length.'
      );
    }
    // Creating the final DataView
    this.datas = new DataView(
      buffer,
      start,
      MIDIFileTrack.HDR_LENGTH + trackLength
    );
    // Trying to find the end of track event
    if (
      !(
        0xff ===
          this.datas.getUint8(MIDIFileTrack.HDR_LENGTH + (trackLength - 3)) &&
        0x2f ===
          this.datas.getUint8(MIDIFileTrack.HDR_LENGTH + (trackLength - 2)) &&
        0x00 ===
          this.datas.getUint8(MIDIFileTrack.HDR_LENGTH + (trackLength - 1))
      )
    ) {
      throw new Error(
        'Invalid MIDIFileTrack (0x' +
          start.toString(16) +
          ') :' +
          ' No track end event found at the expected index' +
          ' (' +
          (MIDIFileTrack.HDR_LENGTH + (trackLength - 1)).toString(16) +
          ').'
      );
    }
  }
}

// Static constants
MIDIFileTrack.HDR_LENGTH = 8;

// Track length
MIDIFileTrack.prototype.getTrackLength = function() {
  return this.datas.getUint32(4);
};

MIDIFileTrack.prototype.setTrackLength = function(trackLength) {
  return this.datas.setUint32(4, trackLength);
};

// Read track contents
MIDIFileTrack.prototype.getTrackContent = function() {
  return new DataView(
    this.datas.buffer,
    this.datas.byteOffset + MIDIFileTrack.HDR_LENGTH,
    this.datas.byteLength - MIDIFileTrack.HDR_LENGTH
  );
};

// Set track content
MIDIFileTrack.prototype.setTrackContent = function(dataView) {
  let origin;
  let destination;
  let i;
  let j;
  // Calculating the track length
  const trackLength = dataView.byteLength - dataView.byteOffset;

  // Track length must size at least like an  empty track (4bytes)
  if (4 > trackLength) {
    throw new Error('Invalid track length, must size at least 4bytes');
  }
  this.datas = new DataView(
    new Uint8Array(MIDIFileTrack.HDR_LENGTH + trackLength).buffer
  );
  // Adding the track header (MTrk)
  this.datas.setUint8(0, 0x4d); // M
  this.datas.setUint8(1, 0x54); // T
  this.datas.setUint8(2, 0x72); // r
  this.datas.setUint8(3, 0x6b); // k
  // Adding the track size
  this.datas.setUint32(4, trackLength);
  // Copying the content
  origin = new Uint8Array(
    dataView.buffer,
    dataView.byteOffset,
    dataView.byteLength
  );
  destination = new Uint8Array(
    this.datas.buffer,
    MIDIFileTrack.HDR_LENGTH,
    trackLength
  );
  for (i = 0, j = origin.length; i < j; i++) {
    destination[i] = origin[i];
  }
};

module.exports = MIDIFileTrack;


'use strict'

const semver = require('semver')

/**
 * Match protocols using semver `~` matching.
 *
 * @param {string} myProtocol
 * @param {string} senderProtocol
 * @param {function(Error, boolean)} callback
 * @returns {undefined}
 * @type {matchHandler}
 */
function matchSemver (myProtocol, senderProtocol, callback) {
  const mps = myProtocol.split('/')
  const sps = senderProtocol.split('/')
  const myName = mps[1]
  const myVersion = mps[2]

  const senderName = sps[1]
  const senderVersion = sps[2]

  if (myName !== senderName) {
    return callback(null, false)
  }
  // does my protocol satisfy the sender?
  const valid = semver.satisfies(myVersion, '~' + senderVersion)

  callback(null, valid)
}

module.exports = matchSemver


'use strict'

const pull = require('pull-stream/pull')
const values = require('pull-stream/sources/values')
const collect = require('pull-stream/sinks/collect')
const pullLP = require('pull-length-prefixed')
const debug = require('debug')

exports = module.exports

function randomId () {
  return ((~~(Math.random() * 1e9)).toString(36))
}

// prefixes a message with a varint
// TODO this is a pull-stream 'creep' (pull stream to add a byte?')
function encode (msg, callback) {
  pull(
    values(Buffer.isBuffer(msg) ? [msg] : [Buffer.from(msg)]),
    pullLP.encode(),
    collect((err, encoded) => {
      if (err) {
        return callback(err)
      }
      callback(null, encoded[0])
    })
  )
}

exports.writeEncoded = (writer, msg, callback) => {
  encode(msg, (err, msg) => {
    if (err) {
      return callback(err)
    }
    writer.write(msg)
  })
}

function createLogger (type) {
  const rId = randomId()

  function printer (logger) {
    return (msg) => {
      if (Array.isArray(msg)) {
        msg = msg.join(' ')
      }
      logger('(%s) %s', rId, msg)
    }
  }

  const log = printer(debug('mss:' + type))
  log.error = printer(debug('mss:' + type + ':error'))

  return log
}

exports.log = {}

exports.log.dialer = () => {
  return createLogger('dialer\t')
}
exports.log.listener = () => {
  return createLogger('listener\t')
}


'use strict'

/**
 * Match protocols exactly.
 *
 * @param {string} myProtocol
 * @param {string} senderProtocol
 * @param {function(Error, boolean)} callback
 * @returns {undefined}
 * @type {matchHandler}
 */
function matchExact (myProtocol, senderProtocol, callback) {
  const result = myProtocol === senderProtocol
  callback(null, result)
}

module.exports = matchExact