/**
 * Event management
 * @module Ink.Dom.Event_1
 * @version 1
 */

Ink.createModule('Ink.Dom.Event', 1, [], function() {
    /* jshint
           asi:true,
           strict:false,
           laxcomma:true,
           eqeqeq:false,
           laxbreak:true,
           boss:true,
           curly:false,
           expr:true
           */

    /**
     * @namespace Ink.Dom.Event_1
     * @static
     */

    /*!
      * Bean - copyright (c) Jacob Thornton 2011-2012
      * https://github.com/fat/bean
      * MIT license
      */
    var bean = (function (name, context, definition) {
      return definition()
    })('bean', this, function (name, context) {
      name    = name    || 'bean'
      context = context || this

      var win            = window
        , old            = context[name]
        , namespaceRegex = /[^\.]*(?=\..*)\.|.*/
        , nameRegex      = /\..*/
        , addEvent       = 'addEventListener'
        , removeEvent    = 'removeEventListener'
        , doc            = document || {}
        , root           = doc.documentElement || {}
        , W3C_MODEL      = root[addEvent]
        , eventSupport   = W3C_MODEL ? addEvent : 'attachEvent'
        , ONE            = {} // singleton for quick matching making add() do one()

        , slice          = Array.prototype.slice
        , str2arr        = function (s, d) { return s.split(d || ' ') }
        , isString       = function (o) { return typeof o == 'string' }
        , isFunction     = function (o) { return typeof o == 'function' }

          // events that we consider to be 'native', anything not in this list will
          // be treated as a custom event
        , standardNativeEvents =
            'click dblclick mouseup mousedown contextmenu '                  + // mouse buttons
            'mousewheel mousemultiwheel DOMMouseScroll '                     + // mouse wheel
            'mouseover mouseout mousemove selectstart selectend '            + // mouse movement
            'keydown keypress keyup '                                        + // keyboard
            'orientationchange '                                             + // mobile
            'focus blur change reset select submit '                         + // form elements
            'load unload beforeunload resize move DOMContentLoaded '         + // window
            'readystatechange message '                                      + // window
            'error abort scroll '                                              // misc
          // element.fireEvent('onXYZ'... is not forgiving if we try to fire an event
          // that doesn't actually exist, so make sure we only do these on newer browsers
        , w3cNativeEvents =
            'show '                                                          + // mouse buttons
            'input invalid '                                                 + // form elements
            'touchstart touchmove touchend touchcancel '                     + // touch
            'gesturestart gesturechange gestureend '                         + // gesture
            'textinput'                                                      + // TextEvent
            'readystatechange pageshow pagehide popstate '                   + // window
            'hashchange offline online '                                     + // window
            'afterprint beforeprint '                                        + // printing
            'dragstart dragenter dragover dragleave drag drop dragend '      + // dnd
            'loadstart progress suspend emptied stalled loadmetadata '       + // media
            'loadeddata canplay canplaythrough playing waiting seeking '     + // media
            'seeked ended durationchange timeupdate play pause ratechange '  + // media
            'volumechange cuechange '                                        + // media
            'checking noupdate downloading cached updateready obsolete '       // appcache

          // convert to a hash for quick lookups
        , nativeEvents = (function (hash, events, i) {
            for (i = 0; i < events.length; i++) events[i] && (hash[events[i]] = 1)
            return hash
          }({}, str2arr(standardNativeEvents + (W3C_MODEL ? w3cNativeEvents : ''))))

          // custom events are events that we *fake*, they are not provided natively but
          // we can use native events to generate them
        , customEvents = (function () {
            var isAncestor = 'compareDocumentPosition' in root
                  ? function (element, container) {
                      return container.compareDocumentPosition && (container.compareDocumentPosition(element) & 16) === 16
                    }
                  : 'contains' in root
                    ? function (element, container) {
                        container = container.nodeType === 9 || container === window ? root : container
                        return container !== element && container.contains(element)
                      }
                    : function (element, container) {
                        while (element = element.parentNode) if (element === container) return 1
                        return 0
                      }
              , check = function (event) {
                  var related = event.relatedTarget
                  return !related
                    ? related == null
                    : (related !== this && related.prefix !== 'xul' && !/document/.test(this.toString())
                        && !isAncestor(related, this))
                }

            return {
                mouseenter: { base: 'mouseover', condition: check }
              , mouseleave: { base: 'mouseout', condition: check }
              , mousewheel: { base: /Firefox/.test(navigator.userAgent) ? 'DOMMouseScroll' : 'mousewheel' }
            }
          }())

          // we provide a consistent Event object across browsers by taking the actual DOM
          // event object and generating a new one from its properties.
        , Event = (function () {
                // a whitelist of properties (for different event types) tells us what to check for and copy
            var commonProps  = str2arr('altKey attrChange attrName bubbles cancelable ctrlKey currentTarget ' +
                  'detail eventPhase getModifierState isTrusted metaKey relatedNode relatedTarget shiftKey '  +
                  'srcElement target timeStamp type view which propertyName path')
              , mouseProps   = commonProps.concat(str2arr('button buttons clientX clientY dataTransfer '      +
                  'fromElement offsetX offsetY pageX pageY screenX screenY toElement movementX movementY region'))
              , mouseWheelProps = mouseProps.concat(str2arr('wheelDelta wheelDeltaX wheelDeltaY wheelDeltaZ ' +
                  'axis')) // 'axis' is FF specific
              , keyProps     = commonProps.concat(str2arr('char charCode key keyCode keyIdentifier '          +
                  'keyLocation location isComposing code'))
              , textProps    = commonProps.concat(str2arr('data'))
              , touchProps   = commonProps.concat(str2arr('touches targetTouches changedTouches scale rotation'))
              , messageProps = commonProps.concat(str2arr('data origin source'))
              , stateProps   = commonProps.concat(str2arr('state'))
              , overOutRegex = /over|out/
                // some event types need special handling and some need special properties, do that all here
              , typeFixers   = [
                    { // key events
                        reg: /key/i
                      , fix: function (event, newEvent) {
                          newEvent.keyCode = event.keyCode || event.which
                          return keyProps
                        }
                    }
                  , { // mouse events
                        reg: /click|mouse(?!(.*wheel|scroll))|menu|drag|drop/i
                      , fix: function (event, newEvent, type) {
                          newEvent.rightClick = event.which === 3 || event.button === 2
                          newEvent.pos = { x: 0, y: 0 }
                          if (event.pageX || event.pageY) {
                            newEvent.clientX = event.pageX
                            newEvent.clientY = event.pageY
                          } else if (event.clientX || event.clientY) {
                            newEvent.clientX = event.clientX + doc.body.scrollLeft + root.scrollLeft
                            newEvent.clientY = event.clientY + doc.body.scrollTop + root.scrollTop
                          }
                          if (overOutRegex.test(type)) {
                            newEvent.relatedTarget = event.relatedTarget
                              || event[(type == 'mouseover' ? 'from' : 'to') + 'Element']
                          }
                          return mouseProps
                        }
                    }
                  , { // mouse wheel events
                        reg: /mouse.*(wheel|scroll)/i
                      , fix: function () { return mouseWheelProps }
                    }
                  , { // TextEvent
                        reg: /^text/i
                      , fix: function () { return textProps }
                    }
                  , { // touch and gesture events
                        reg: /^touch|^gesture/i
                      , fix: function () { return touchProps }
                    }
                  , { // message events
                        reg: /^message$/i
                      , fix: function () { return messageProps }
                    }
                  , { // popstate events
                        reg: /^popstate$/i
                      , fix: function () { return stateProps }
                    }
                  , { // everything else
                        reg: /.*/
                      , fix: function () { return commonProps }
                    }
                ]
              , typeFixerMap = {} // used to map event types to fixer functions (above), a basic cache mechanism

              , Event = function (event, element, isNative) {
                  if (!arguments.length) return
                  event = event || ((element.ownerDocument || element.document || element).parentWindow || win).event
                  this.originalEvent = event
                  this.isNative       = isNative
                  this.isBean         = true

                  if (!event) return

                  var type   = event.type
                    , target = event.target || event.srcElement
                    , i, l, p, props, fixer

                  this.target = target && target.nodeType === 3 ? target.parentNode : target

                  if (isNative) { // we only need basic augmentation on custom events, the rest expensive & pointless
                    fixer = typeFixerMap[type]
                    if (!fixer) { // haven't encountered this event type before, map a fixer function for it
                      for (i = 0, l = typeFixers.length; i < l; i++) {
                        if (typeFixers[i].reg.test(type)) { // guaranteed to match at least one, last is .*
                          typeFixerMap[type] = fixer = typeFixers[i].fix
                          break
                        }
                      }
                    }

                    props = fixer(event, this, type)
                    for (i = props.length; i--;) {
                      if (!((p = props[i]) in this) && p in event) this[p] = event[p]
                    }
                  }
                }

            // preventDefault() and stopPropagation() are a consistent interface to those functions
            // on the DOM, stop() is an alias for both of them together
            Event.prototype.preventDefault = function () {
              if (this.originalEvent.preventDefault) this.originalEvent.preventDefault()
              else try { this.originalEvent.returnValue = false } catch(e) {}
            }
            Event.prototype.stopPropagation = function () {
              if (this.originalEvent.stopPropagation) this.originalEvent.stopPropagation()
              else this.originalEvent.cancelBubble = true
            }
            Event.prototype.stop = function () {
              this.preventDefault()
              this.stopPropagation()
              this.stopped = true
            }
            // stopImmediatePropagation() has to be handled internally because we manage the event list for
            // each element
            // note that originalElement may be a Bean#Event object in some situations
            Event.prototype.stopImmediatePropagation = function () {
              if (this.originalEvent.stopImmediatePropagation) this.originalEvent.stopImmediatePropagation()
              this.isImmediatePropagationStopped = function () { return true }
            }
            Event.prototype.isImmediatePropagationStopped = function () {
              return this.originalEvent.isImmediatePropagationStopped && this.originalEvent.isImmediatePropagationStopped()
            }
            Event.prototype.clone = function (currentTarget) {
              //TODO: this is ripe for optimisation, new events are *expensive*
              // improving this will speed up delegated events
              var ne = new Event(this, this.element, this.isNative)
              ne.currentTarget = currentTarget
              return ne
            }

            return Event
          }())

          // if we're in old IE we can't do onpropertychange on doc or win so we use doc.documentElement for both
        , targetElement = function (element, isNative) {
            return !W3C_MODEL && !isNative && (element === doc || element === win) ? root : element
          }

          /**
            * Bean maintains an internal registry for event listeners. We don't touch elements, objects
            * or functions to identify them, instead we store everything in the registry.
            * Each event listener has a RegEntry object, we have one 'registry' for the whole instance.
            */
        , RegEntry = (function () {
            // each handler is wrapped so we can handle delegation and custom events
            var wrappedHandler = function (element, fn, condition, args) {
                var call = function (event, eargs) {
                      return fn.apply(element, args ? slice.call(eargs, event ? 0 : 1).concat(args) : eargs)
                    }
                  , findTarget = function (event, eventElement) {
                      return fn.__beanDel ? fn.__beanDel.ft(event.target, element) : eventElement
                    }
                  , handler = condition
                      ? function (event) {
                          var target = findTarget(event, this) // deleated event
                          if (condition.apply(target, arguments)) {
                            if (event) event.currentTarget = target
                            return call(event, arguments)
                          }
                        }
                      : function (event) {
                          if (fn.__beanDel) event = event.clone(findTarget(event)) // delegated event, fix the fix
                          return call(event, arguments)
                        }
                handler.__beanDel = fn.__beanDel
                return handler
              }

            , RegEntry = function (element, type, handler, original, namespaces, args, root) {
                var customType     = customEvents[type]
                  , isNative

                if (type == 'unload') {
                  // self clean-up
                  handler = once(removeListener, element, type, handler, original)
                }

                if (customType) {
                  if (customType.condition) {
                    handler = wrappedHandler(element, handler, customType.condition, args)
                  }
                  type = customType.base || type
                }

                this.isNative      = isNative = nativeEvents[type] && !!element[eventSupport]
                this.customType    = !W3C_MODEL && !isNative && type
                this.element       = element
                this.type          = type
                this.original      = original
                this.namespaces    = namespaces
                this.eventType     = W3C_MODEL || isNative ? type : 'propertychange'
                this.target        = targetElement(element, isNative)
                this[eventSupport] = !!this.target[eventSupport]
                this.root          = root
                this.handler       = wrappedHandler(element, handler, null, args)
              }

            // given a list of namespaces, is our entry in any of them?
            RegEntry.prototype.inNamespaces = function (checkNamespaces) {
              var i, j, c = 0
              if (!checkNamespaces) return true
              if (!this.namespaces) return false
              for (i = checkNamespaces.length; i--;) {
                for (j = this.namespaces.length; j--;) {
                  if (checkNamespaces[i] == this.namespaces[j]) c++
                }
              }
              return checkNamespaces.length === c
            }

            // match by element, original fn (opt), handler fn (opt)
            RegEntry.prototype.matches = function (checkElement, checkOriginal, checkHandler) {
              return this.element === checkElement &&
                (!checkOriginal || this.original === checkOriginal) &&
                (!checkHandler || this.handler === checkHandler)
            }

            return RegEntry
          }())

        , registry = (function () {
            // our map stores arrays by event type, just because it's better than storing
            // everything in a single array.
            // uses '$' as a prefix for the keys for safety and 'r' as a special prefix for
            // rootListeners so we can look them up fast
            var map = {}

              // generic functional search of our registry for matching listeners,
              // `fn` returns false to break out of the loop
              , forAll = function (element, type, original, handler, root, fn) {
                  var pfx = root ? 'r' : '$'
                  if (!type || type == '*') {
                    // search the whole registry
                    for (var t in map) {
                      if (t.charAt(0) == pfx) {
                        forAll(element, t.substr(1), original, handler, root, fn)
                      }
                    }
                  } else {
                    var i = 0, l, list = map[pfx + type], all = element == '*'
                    if (!list) return
                    for (l = list.length; i < l; i++) {
                      if ((all || list[i].matches(element, original, handler)) && !fn(list[i], list, i, type)) return
                    }
                  }
                }

              , has = function (element, type, original, root) {
                  // we're not using forAll here simply because it's a bit slower and this
                  // needs to be fast
                  var i, list = map[(root ? 'r' : '$') + type]
                  if (list) {
                    for (i = list.length; i--;) {
                      if (!list[i].root && list[i].matches(element, original, null)) return true
                    }
                  }
                  return false
                }

              , get = function (element, type, original, root) {
                  var entries = []
                  forAll(element, type, original, null, root, function (entry) {
                    return entries.push(entry)
                  })
                  return entries
                }

              , put = function (entry) {
                  var has = !entry.root && !this.has(entry.element, entry.type, null, false)
                    , key = (entry.root ? 'r' : '$') + entry.type
                  ;(map[key] || (map[key] = [])).push(entry)
                  return has
                }

              , del = function (entry) {
                  forAll(entry.element, entry.type, null, entry.handler, entry.root, function (entry, list, i) {
                    list.splice(i, 1)
                    entry.removed = true
                    if (list.length === 0) delete map[(entry.root ? 'r' : '$') + entry.type]
                    return false
                  })
                }

                // dump all entries, used for onunload
              , entries = function () {
                  var t, entries = []
                  for (t in map) {
                    if (t.charAt(0) == '$') entries = entries.concat(map[t])
                  }
                  return entries
                }

            return { has: has, get: get, put: put, del: del, entries: entries }
          }())

          // we need a selector engine for delegated events, use querySelectorAll if it exists
          // but for older browsers we need Qwery, Sizzle or similar
        , selectorEngine
        , setSelectorEngine = function (e) {
            if (!arguments.length) {
              selectorEngine = doc.querySelectorAll
                ? function (s, r) {
                    return r.querySelectorAll(s)
                  }
                : function () {
                    throw new Error('Bean: No selector engine installed') // eeek
                  }
            } else {
              selectorEngine = e
            }
          }

          // we attach this listener to each DOM event that we need to listen to, only once
          // per event type per DOM element
        , rootListener = function (event, type) {
            if (!W3C_MODEL && type && event && event.propertyName != '_on' + type) return

            var listeners = registry.get(this, type || event.type, null, false)
              , l = listeners.length
              , i = 0

            event = new Event(event, this, true)
            if (type) event.type = type

            // iterate through all handlers registered for this type, calling them unless they have
            // been removed by a previous handler or stopImmediatePropagation() has been called
            for (; i < l && !event.isImmediatePropagationStopped(); i++) {
              if (!listeners[i].removed) listeners[i].handler.call(this, event)
            }
          }

          // add and remove listeners to DOM elements
        , listener = W3C_MODEL
            ? function (element, type, add) {
                // new browsers
                element[add ? addEvent : removeEvent](type, rootListener, false)
              }
            : function (element, type, add, custom) {
                // IE8 and below, use attachEvent/detachEvent and we have to piggy-back propertychange events
                // to simulate event bubbling etc.
                var entry
                if (add) {
                  registry.put(entry = new RegEntry(
                      element
                    , custom || type
                    , function (event) { // handler
                        rootListener.call(element, event, custom)
                      }
                    , rootListener
                    , null
                    , null
                    , true // is root
                  ))
                  if (custom && element['_on' + custom] == null) element['_on' + custom] = 0
                  entry.target.attachEvent('on' + entry.eventType, entry.handler)
                } else {
                  entry = registry.get(element, custom || type, rootListener, true)[0]
                  if (entry) {
                    entry.target.detachEvent('on' + entry.eventType, entry.handler)
                    registry.del(entry)
                  }
                }
              }

        , once = function (rm, element, type, fn, originalFn) {
            // wrap the handler in a handler that does a remove as well
            return function () {
              fn.apply(this, arguments)
              rm(element, type, originalFn)
            }
          }

        , removeListener = function (element, orgType, handler, namespaces) {
            var type     = orgType && orgType.replace(nameRegex, '')
              , handlers = registry.get(element, type, null, false)
              , removed  = {}
              , i, l

            for (i = 0, l = handlers.length; i < l; i++) {
              if ((!handler || handlers[i].original === handler) && handlers[i].inNamespaces(namespaces)) {
                // TODO: this is problematic, we have a registry.get() and registry.del() that
                // both do registry searches so we waste cycles doing this. Needs to be rolled into
                // a single registry.forAll(fn) that removes while finding, but the catch is that
                // we'll be splicing the arrays that we're iterating over. Needs extra tests to
                // make sure we don't screw it up. @rvagg
                registry.del(handlers[i])
                if (!removed[handlers[i].eventType] && handlers[i][eventSupport])
                  removed[handlers[i].eventType] = { t: handlers[i].eventType, c: handlers[i].type }
              }
            }
            // check each type/element for removed listeners and remove the rootListener where it's no longer needed
            for (i in removed) {
              if (removed.hasOwnProperty(i)) {
                if (!registry.has(element, removed[i].t, null, false)) {
                  // last listener of this type, remove the rootListener
                  listener(element, removed[i].t, false, removed[i].c)
                }
              }
            }
          }

          // set up a delegate helper using the given selector, wrap the handler function
        , delegate = function (selector, fn) {
            //TODO: findTarget (therefore $) is called twice, once for match and once for
            // setting e.currentTarget, fix this so it's only needed once
            var findTarget = function (target, root) {
                  var i, array = isString(selector) ? selectorEngine(selector, root) : selector
                  for (; target && target !== root; target = target.parentNode) {
                    for (i = array.length; i--;) {
                      if (array[i] === target) return target
                    }
                  }
                }
              , handler = function (e) {
                  var match = findTarget(e.target, this)
                  if (match) fn.apply(match, arguments)
                }

            // __beanDel isn't pleasant but it's a private function, not exposed outside of Bean
            handler.__beanDel = {
                ft       : findTarget // attach it here for customEvents to use too
              , selector : selector
            }
            return handler
          }

        , fireListener = W3C_MODEL ? function (isNative, type, element) {
            // modern browsers, do a proper dispatchEvent()
            var evt = doc.createEvent(isNative ? 'HTMLEvents' : 'UIEvents')
            evt[isNative ? 'initEvent' : 'initUIEvent'](type, true, true, win, 1)
            element.dispatchEvent(evt)
          } : function (isNative, type, element) {
            // old browser use onpropertychange, just increment a custom property to trigger the event
            element = targetElement(element, isNative)
            isNative ? element.fireEvent('on' + type, doc.createEventObject()) : element['_on' + type]++
          }

          /**
            * Public API: off(), on(), add(), (remove()), one(), fire(), clone()
            */

          /**
            * off(element[, eventType(s)[, handler ]])
            */
        , off = function (element, typeSpec, fn) {
            var isTypeStr = isString(typeSpec)
              , k, type, namespaces, i

            if (isTypeStr && typeSpec.indexOf(' ') > 0) {
              // off(el, 't1 t2 t3', fn) or off(el, 't1 t2 t3')
              typeSpec = str2arr(typeSpec)
              for (i = typeSpec.length; i--;)
                off(element, typeSpec[i], fn)
              return element
            }

            type = isTypeStr && typeSpec.replace(nameRegex, '')
            if (type && customEvents[type]) type = customEvents[type].base

            if (!typeSpec || isTypeStr) {
              // off(el) or off(el, t1.ns) or off(el, .ns) or off(el, .ns1.ns2.ns3)
              if (namespaces = isTypeStr && typeSpec.replace(namespaceRegex, '')) namespaces = str2arr(namespaces, '.')
              removeListener(element, type, fn, namespaces)
            } else if (isFunction(typeSpec)) {
              // off(el, fn)
              removeListener(element, null, typeSpec)
            } else {
              // off(el, { t1: fn1, t2, fn2 })
              for (k in typeSpec) {
                if (typeSpec.hasOwnProperty(k)) off(element, k, typeSpec[k])
              }
            }

            return element
          }

          /**
            * on(element, eventType(s)[, selector], handler[, args ])
            */
        , on = function(element, events, selector, fn) {
            var originalFn, type, types, i, args, entry, first

            //TODO: the undefined check means you can't pass an 'args' argument, fix this perhaps?
            if (selector === undefined && typeof events == 'object') {
              //TODO: this can't handle delegated events
              for (type in events) {
                if (events.hasOwnProperty(type)) {
                  on.call(this, element, type, events[type])
                }
              }
              return
            }

            if (!isFunction(selector)) {
              // delegated event
              originalFn = fn
              args       = slice.call(arguments, 4)
              fn         = delegate(selector, originalFn, selectorEngine)
            } else {
              args       = slice.call(arguments, 3)
              fn         = originalFn = selector
            }

            types = str2arr(events)

            // special case for one(), wrap in a self-removing handler
            if (this === ONE) {
              fn = once(off, element, events, fn, originalFn)
            }

            for (i = types.length; i--;) {
              // add new handler to the registry and check if it's the first for this element/type
              first = registry.put(entry = new RegEntry(
                  element
                , types[i].replace(nameRegex, '') // event type
                , fn
                , originalFn
                , str2arr(types[i].replace(namespaceRegex, ''), '.') // namespaces
                , args
                , false // not root
              ))
              if (entry[eventSupport] && first) {
                // first event of this type on this element, add root listener
                listener(element, entry.eventType, true, entry.customType)
              }
            }

            return element
          }

          /**
            * add(element[, selector], eventType(s), handler[, args ])
            *
            * Deprecated: kept (for now) for backward-compatibility
            */
        , add = function (element, events, fn, delfn) {
            return on.apply(
                null
              , !isString(fn)
                  ? slice.call(arguments)
                  : [ element, fn, events, delfn ].concat(arguments.length > 3 ? slice.call(arguments, 5) : [])
            )
          }

          /**
            * one(element, eventType(s)[, selector], handler[, args ])
            */
        , one = function () {
            return on.apply(ONE, arguments)
          }

          /**
            * fire(element, eventType(s)[, args ])
            *
            * The optional 'args' argument must be an array, if no 'args' argument is provided
            * then we can use the browser's DOM event system, otherwise we trigger handlers manually
            */
        , fire = function (element, type, args) {
            var types = str2arr(type)
              , i, j, l, names, handlers

            for (i = types.length; i--;) {
              type = types[i].replace(nameRegex, '')
              if (names = types[i].replace(namespaceRegex, '')) names = str2arr(names, '.')
              if (!names && !args && element[eventSupport]) {
                fireListener(nativeEvents[type], type, element)
              } else {
                // non-native event, either because of a namespace, arguments or a non DOM element
                // iterate over all listeners and manually 'fire'
                handlers = registry.get(element, type, null, false)
                args = [false].concat(args)
                for (j = 0, l = handlers.length; j < l; j++) {
                  if (handlers[j].inNamespaces(names)) {
                    handlers[j].handler.apply(element, args)
                  }
                }
              }
            }
            return element
          }

          /**
            * clone(dstElement, srcElement[, eventType ])
            *
            * TODO: perhaps for consistency we should allow the same flexibility in type specifiers?
            */
        , clone = function (element, from, type) {
            var handlers = registry.get(from, type, null, false)
              , l = handlers.length
              , i = 0
              , args, beanDel

            for (; i < l; i++) {
              if (handlers[i].original) {
                args = [ element, handlers[i].type ]
                if (beanDel = handlers[i].handler.__beanDel) args.push(beanDel.selector)
                args.push(handlers[i].original)
                on.apply(null, args)
              }
            }
            return element
          }

        , bean = {
              'on'                : on
            , 'add'               : add
            , 'one'               : one
            , 'off'               : off
            , 'remove'            : off
            , 'clone'             : clone
            , 'fire'              : fire
            , 'Event'             : Event
            , 'setSelectorEngine' : setSelectorEngine
            , 'noConflict'        : function () {
                context[name] = old
                return this
              }
          }

      // for IE, clean up on unload to avoid leaks
      if (win.attachEvent) {
        var cleanup = function () {
          var i, entries = registry.entries()
          for (i in entries) {
            if (entries[i].type && entries[i].type !== 'unload') off(entries[i].element, entries[i].type)
          }
          win.detachEvent('onunload', cleanup)
          win.CollectGarbage && win.CollectGarbage()
        }
        win.attachEvent('onunload', cleanup)
      }

      // initialize selector engine to internal default (qSA or throw Error)
      setSelectorEngine(Ink.ss)

      return bean
    });

    /**
     * Keep this declaration here and off Bean as it extends the Event
     * object and some properties are readonly in strict mode
     */
    'use strict';

    var InkEvent = {

    KEY_BACKSPACE: 8,
    KEY_TAB:       9,
    KEY_RETURN:   13,
    KEY_ESC:      27,
    KEY_SPACE:    32,
    KEY_LEFT:     37,
    KEY_UP:       38,
    KEY_RIGHT:    39,
    KEY_DOWN:     40,
    KEY_DELETE:   46,
    KEY_HOME:     36,
    KEY_END:      35,
    KEY_PAGEUP:   33,
    KEY_PAGEDOWN: 34,
    KEY_INSERT:   45,
    
    /**
     * Creates a throttled version of a function.
     * Returns a function which calls `func`, waiting at least `wait` milliseconds between calls. This is useful for events such as `scroll` or `resize`, which can be triggered too many times per second, slowing down the browser with needless function calls.
     *
     * *note:* This does not delay the first function call to the function.
     *
     * @method throttle
     * @param {Function} func   Function to call. Arguments and context are both passed.
     * @param {Number} [wait]=0 Milliseconds to wait between calls.
     * @param {Object} [options={}] Options object, containing:
     * @param {Boolean} [options.preventDefault=false] Whether to call preventDefault() on events received here. Use this to throttle mousemove events which you also want to preventDefault() because throttle will not call your function all the time so you don't get a chance to preventDefault() all the events, altough you might need to.
     * @param {Mixed} [options.bind] The throttled function is bound to this context. Otherwise, it will use whatever `this` it gets. Just a shorthand of also calling Ink.bind(context, func) on the function after throttling it.
     * @return {Function} A function throttled which will only be called at most every `wait` milliseconds.
     * @sample Ink_Dom_Event_1_throttle.html 
     **/
    throttle: function (func, wait, opt) {
        wait = wait || 0;
        opt = opt || {};
        var lastCall = 0;  // Warning: This breaks on Jan 1st 1970 0:00
        var timeout;
        function throttled(maybeEvent) {
            var now = +new Date();
            var timeDiff = now - lastCall;
            if (opt.preventDefault &&
                    maybeEvent &&
                    typeof maybeEvent.preventDefault === 'function') {
                maybeEvent.preventDefault();
            }
            if (timeDiff >= wait) {
                lastCall = now;
                return func.apply('bind' in opt ? opt.bind : this, [].slice.call(arguments));
            } else {
                var that = this;
                var args = [].slice.call(arguments);

                if (timeout) {
                    // Clear the old timeout because that one has old arguments
                    clearTimeout(timeout);
                }

                timeout = setTimeout(function () {
                    timeout = null;
                    return throttled.apply(that, args);
                }, wait - timeDiff);
            }
        }
        return throttled;
    },

    /**
     * Gets the event's target element.
     *
     * @method element
     * @param {Object} ev  Event object
     * @return {Element} The target
     * @public
     * @sample Ink_Dom_Event_1_element.html 
     */
    element: function(ev) {
        var node = ev.delegationTarget ||
            ev.target ||
            // IE stuff
            (ev.type === 'mouseout'   && ev.fromElement) ||
            (ev.type === 'mouseleave' && ev.fromElement) ||
            (ev.type === 'mouseover'  && ev.toElement) ||
            (ev.type === 'mouseenter' && ev.toElement) ||
            ev.srcElement ||
            null;
        return node && (node.nodeType === 3 || node.nodeType === 4) ? node.parentNode : node;
    },

    /**
     * Gets the event's related target element.
     *
     * @method relatedTarget
     * @param {Object} ev event object
     * @return {DOMNode} The related target
     * @sample Ink_Dom_Event_1_relatedTarget.html 
     */
    relatedTarget: function(ev){
        var node = ev.relatedTarget ||
            // IE stuff
            (ev.type === 'mouseout'   && ev.toElement) ||
            (ev.type === 'mouseleave' && ev.toElement) ||
            (ev.type === 'mouseover'  && ev.fromElement) ||
            (ev.type === 'mouseenter' && ev.fromElement) ||
            null;
        return node && (node.nodeType === 3 || node.nodeType === 4) ? node.parentNode : node;
    },

    /**
     * Find closest ancestor element by tag name related to the event target.
     * Navigate up the DOM tree, looking for a tag with the name `elmTagName`.
     *
     * If such tag is not found, `document` is returned.
     *
     * @method findElement
     * @param {Object}  ev              Event object
     * @param {String}  elmTagName      Tag name to find
     * @param {Boolean} [force]=false   Flag to skip returning `document` and to return `false` instead.
     * @return {DOMElement} the first element which matches given tag name or the document element if the wanted tag is not found
     * @sample Ink_Dom_Event_1_findElement.html 
     */
    findElement: function(ev, elmTagName, force)
    {
        var node = this.element(ev);
        while(true) {
            if(node.nodeName.toLowerCase() === elmTagName.toLowerCase()) {
                return node;
            } else {
                node = node.parentNode;
                if(!node) {
                    if(force) {
                        return false;
                    }
                    return document;
                }
                if(!node.parentNode){
                    if(force){ return false; }
                    return document;
                }
            }
        }
    },

    /**
     * Attaches an event to element
     *
     * @method observe
     * @param {DOMElement|String}  element      Element id or element
     * @param {String}             eventName    Event name
     * @param {Function}           callBack     Receives the event object as a parameter. If you're manually firing custom events, check it's eventName property to make sure you're handling the right event.
     * @param {Boolean}            [useCapture] Flag to change event listening from bubbling to capture.
     * @return {Function} The event handler used. Hang on to this if you want to `stopObserving` later.
     * @sample Ink_Dom_Event_1_observe.html 
     */
    observe: function(element, eventName, callBack, useCapture) {
        element = Ink.i(element);
        if(element) {
            if(element.addEventListener) {
                element.addEventListener(eventName, callBack, !!useCapture);
            } else {
                element.attachEvent('on' + eventName, (callBack = Ink.bind(callBack, element)));
            }
            return callBack;
        }
    },

    /**
     * Like observe, but listen to the event only once.
     *
     * @method observeOnce
     * @param {DOMElement|String}  element      Element id or element
     * @param {String}             eventName    Event name
     * @param {Function}           callBack     Receives the event object as a parameter. If you're manually firing custom events, check it's eventName property to make sure you're handling the right event.
     * @param {Boolean}            [useCapture] Flag to change event listening from bubbling to capture.
     * @return {Function} The event handler used. Hang on to this if you want to `stopObserving` later.
     * @sample Ink_Dom_Event_1_observeOnce.html 
     */
    observeOnce: function (element, eventName, callBack, useCapture) {
        var onceBack = function () {
            InkEvent.stopObserving(element, eventName, handler);
            return callBack.apply(this, arguments);
        };
        var handler = InkEvent.observe(element, eventName, onceBack, useCapture);
        return handler;
    },

    /**
     * Attaches an event to a selector or array of elements.
     *
     * @method observeMulti
     * @param {Array|String}        elements     Array of elements which are going to be observed. A selector is acceptable too.
     * @param {String}              eventName    Event name
     * @param {Function}            callBack     Receives the event object as a parameter. If you're manually firing custom events, check it's eventName property to make sure you're handling the right event.
     * @param {Boolean}            [useCapture]  Flag change event listening from bubbling to capture.
     * @return {Function} The used callback.
     * @sample Ink_Dom_Event_1_observeMulti.html 
     */
    observeMulti: function (elements, eventName, callBack, useCapture) {
        if (typeof elements === 'string') {
            elements = Ink.ss(elements);
        } else if ( /* is an element */ elements && elements.nodeType === 1) {
            elements = [elements];
        }
        if (!elements[0]) { return false; }

        for (var i = 0, len = elements.length; i < len; i++) {
            this.observe(elements[i], eventName, callBack, useCapture);
        }
        return callBack;
    },

    /**
     * Observes an event on an element and its descendants matching the selector.
     *
     * Requires Ink.Dom.Selector if you need to use a selector.
     *
     * @method observeDelegated
     * @param {Element|String} element   Element to observe.
     * @param {String}         eventName Event name to observe.
     * @param {String}         selector  Child element selector. When null, finds any element.
     * @param {Function}       callback  Callback to be called when the event is fired
     * @return {Function} The used callback, for ceasing to listen to the event later.
     * @public
     * @sample Ink_Dom_Event_1_observeDelegated.html 
     **/
    observeDelegated: function (element, eventName, selector, callback) {
        return InkEvent.observe(element, eventName, function (event) {
            var fromElement = InkEvent.element(event);
            if (!fromElement || fromElement === element) { return; }

            var cursor = fromElement;

            // Go up the document tree until we hit the element itself.
            while (cursor !== element && cursor !== document && cursor) {
                if (Ink.Dom.Selector_1.matchesSelector(cursor, selector)) {
                    event.delegationTarget = cursor;
                    return callback(event);
                }
                cursor = cursor.parentNode;
            }
        });
    },

    /**
     * Removes an event attached to an element.
     *
     * @method stopObserving
     * @param {DOMElement|String}  element       Element id or element
     * @param {String}             eventName     Event name
     * @param {Function}           callBack      Callback function
     * @param {Boolean}            [useCapture]  Set to true if the event was being observed with useCapture set to true as well.
     * @return {void}
     * @public
     * @sample Ink_Dom_Event_1_stopObserving.html 
     */
    stopObserving: function(element, eventName, callBack, useCapture) {
        element = Ink.i(element);

        if(element) {
            if(element.removeEventListener) {
                element.removeEventListener(eventName, callBack, !!useCapture);
            } else {
                element.detachEvent('on' + eventName, callBack);
            }
        }
    },

    /**
     * Stops event propagation and bubbling.
     *
     * @method stop
     * @param {Object} event  Event handle
     * @return {void}
     * @public
     * @sample Ink_Dom_Event_1_stop.html 
     */
    stop: function(event)
    {
        if(event.cancelBubble !== null) {
            event.cancelBubble = true;
        }
        if(event.stopPropagation) {
            event.stopPropagation();
        }
        if(event.preventDefault) {
            event.preventDefault();
        }
        if(window.attachEvent) {
            event.returnValue = false;
        }
        if(event.cancel !== null) {
            event.cancel = true;
        }
    },

    /**
     * Stops event propagation.
     *
     * @method stopPropagation
     * @param {Object} event  Event handle
     * @return {void}
     * @public
     * @sample Ink_Dom_Event_1_stopPropagation.html 
     */
    stopPropagation: function(event) {
        if(event.cancelBubble !== null) {
            event.cancelBubble = true;
        }
        if(event.stopPropagation) {
            event.stopPropagation();
        }
    },

    /**
     * Stops event default behaviour.
     *
     * @method stopDefault
     * @param {Object} event  Event handle
     * @return {void}
     * @public
     * @sample Ink_Dom_Event_1_stopDefault.html 
     */
    stopDefault: function(event)
    {
        if(event.preventDefault) {
            event.preventDefault();
        }
        if(window.attachEvent) {
            event.returnValue = false;
        }
        if(event.cancel !== null) {
            event.cancel = true;
        }
    },

    /**
     * Gets the pointer's coordinates from the event object.
     *
     * @method pointer
     * @param {Object} ev Event object
     * @return {Object} An object with the mouse X and Y position
     * @public
     * @sample Ink_Dom_Event_1_pointer.html 
     */
    pointer: function(ev)
    {
        return {
            x: this.pointerX(ev),
            y: this.pointerY(ev)
        };
    },

    /**
     * Gets the pointer's X coordinate.
     *
     * @method pointerX
     * @param {Object} ev Event object
     * @return {Number} Mouse X position
     * @public
     */
    pointerX: function(ev)
    {
        return (ev.touches && ev.touches[0] && ev.touches[0].pageX) ||
            (ev.pageX) ||
            (ev.clientX);
    },

    /**
     * Gets the pointer's Y coordinate.
     *
     * @method pointerY
     * @param {Object} ev Event object
     * @return {Number} Mouse Y position
     * @public
     */
    pointerY: function(ev)
    {
        return (ev.touches && ev.touches[0] && ev.touches[0].pageY) ||
            (ev.pageY) ||
            (ev.clientY);
    },

    /**
     * Checks if an event is a left click.
     *
     * @method isLeftClick
     * @param {Object} ev  Event object
     * @return {Boolean} True if the event is a left click
     * @public
     * @sample Ink_Dom_Event_1_isLeftClick.html 
     */
    isLeftClick: function(ev) {
        if (window.addEventListener) {
            if(ev.button === 0){
                return true;
            } else if(ev.type === 'touchend' && ev.button === null){
                // [todo] do the above check for pointerEvents too
                return true;
            }
        }
        else {
            if(ev.button === 1){ return true; }
        }
        return false;
    },

    /**
     * Checks if an event is a right click.
     *
     * @method isRightClick
     * @param {Object} ev  Event object
     * @return {Boolean} True if the event is a right click
     * @public
     * @sample Ink_Dom_Event_1_isRightClick.html 
     */
    isRightClick: function(ev) {
        return (ev.button === 2);
    },

    /**
     * Checks if an event is a middle click.
     *
     * @method isMiddleClick
     * @param {Object} ev  Event object
     * @return {Boolean} True if the event is a middle click
     * @public
     * @sample Ink_Dom_Event_1_isMiddleClick.html 
     */
    isMiddleClick: function(ev) {
        if (window.addEventListener) {
            return (ev.button === 1);
        }
        else {
            return (ev.button === 4);
        }
        return false;
    },

    /**
     * Gets character from an event.
     *
     * @method getCharFromKeyboardEvent
     * @param {Object}   event           Keyboard event
     * @param {Boolean}  [changeCasing]  If true uppercases, if false lowercases, otherwise keeps casing
     * @return {String} Character representation of pressed key combination
     * @public
     * @sample Ink_Dom_Event_1_getCharFromKeyboardEvent.html 
     */
    getCharFromKeyboardEvent: function(event, changeCasing) {
        var k = event.keyCode;
        var c = String.fromCharCode(k);

        var shiftOn = event.shiftKey;
        if (k >= 65 && k <= 90) {   // A-Z
            if (typeof changeCasing === 'boolean') {
                shiftOn = changeCasing;
            }
            return (shiftOn) ? c : c.toLowerCase();
        }
        else if (k >= 96 && k <= 105) { // numpad digits
            return String.fromCharCode( 48 + (k-96) );
        }
        switch (k) {
            case 109:   case 189:   return '-';
            case 107:   case 187:   return '+';
        }
        return c;
    },

    debug: function(){}
};

/**
 * Lets you attach event listeners to both elements and objects.
 * http://github.com/fat/bean#on
 *
 * @method on
 * @param {DOMElement|Object} element An HTML DOM element or any JavaScript Object
 * @param {String}            eventType An Event (or multiple events, space separated) to listen to
 * @param {String}            [selector] A CSS DOM Element selector string to bind the listener to child elements matching the selector
 * @param {Function}          [handler] The callback function
 * @param {Object}            [args...] Additional arguments to pass to the callback function when triggered
 * 
 * @return {DOMElement|Object} Returns the original DOM Element or Javascript Object
 * @public
 * @sample Ink_Dom_Event_1_on.html 
 */

/**
 * Alias for `on` but will only be executed once.
 * bean.one() is an alias for bean.on() except that the handler will only be executed once and then removed for the event type(s).
 * http://github.com/fat/bean#one
 *
 * @method one
 * @param {DOMElement|Object} element An HTML DOM element or any JavaScript Object
 * @param {String}            eventType An Event (or multiple events, space separated) to listen to
 * @param {String}            [selector] A CSS DOM Element selector string to bind the listener to child elements matching the selector
 * @param {Function}          [handler] The callback function
 * @param                     [args...] Additional arguments to pass to the callback function when triggered
 * 
 * @return {DOMElement|Object} Returns the original DOM Element or Javascript Object
 * @public
 * @sample Ink_Dom_Event_1_one.html 
 */

/**
 * Removes event handlers.
 * bean.off() is how you get rid of handlers once you no longer want them active. It's also a good idea to call off on elements before you remove them from your DOM; this gives Bean a chance to clean up some things and prevents memory leaks.
 * http://github.com/fat/bean#off
 *
 * @method off
 * @param {DOMElement|Object} element An HTML DOM element or any JavaScript Object
 * @param {String}            eventType An Event (or multiple events, space separated) to remove
 * @param {Function}          [handler] The specific callback function to remove
 * 
 * @return {DOMElement|Object} Returns the original DOM Element or Javascript Object
 * @public
 * @sample Ink_Dom_Event_1_off.html 
 */

/**
 * Clones events from one object to another
 * bean.clone() is a method for cloning events from one DOM element or object to another.
 * http://github.com/fat/bean#clone
 *
 * @method clone
 * @param {DOMElement|Object} destElement An HTML DOM element or any JavaScript Object to copy events to
 * @param {String}            srcElement An HTML DOM element or any JavaScript Object to copy events from
 * @param {String}            [eventType] An Event (or multiple events, space separated) to clone
 * 
 * @return {DOMElement|Object} Returns the original DOM Element or Javascript Object
 * @public
 * @sample Ink_Dom_Event_1_clone.html 
 */

/**
 * Triggers events.
 * http://github.com/fat/bean#fire
 *
 * @method fire
 * @param {DOMElement|Object} destElement An HTML DOM element or any JavaScript Object fire the event on
 * @param {String}            eventType An Event (or multiple events, space separated) to fire
 * @param                     [args...] Additional arguments to pass to the callback function when triggered
 *
 * @return {DOMElement|Object} Returns the original DOM Element or Javascript Object
 * @public
 * @sample Ink_Dom_Event_1_fire.html 
 */

return Ink.extendObj(InkEvent, bean);

});
