/**
 * Custom event "mouse:wheel" - fire on mousewheel or DOMMouseScroll
 * @type {Object}
 * @name pulp.event.custom.mouse:wheel
 */
pulp.event.custom['mouse:wheel'] = {
  /** @ignore */
  add: function(node, type, handler) {
    return {
      type: window.addEventListener ? 'DOMMouseScroll' : 'mousewheel'
    };
  },
  /** @ignore */
  remove: function(node, type, handler) {
    return {
      type: window.addEventListener ? 'DOMMouseScroll' : 'mousewheel'
    };
  }
};

// shift+click, 
// pulp.event.custom.js plugin "shift+click" - (and "alt+click", "ctrl+click", "meta+click") fire on key+click combination
pulp.base.each('shift alt ctrl meta', /** @ignore */function(key) { 
  pulp.event.custom[key + '+click'] = {
    /** @ignore */
    add: function(node, type, handler) {
      return {
        type: 'click',
        /** @ignore */
        handler: function(event) {
          if (event[key + 'Key']) {
            handler.call(node, event);
          }
          return node;
        }
      };
    },
    /** @ignore */
    remove: function(node, type, handler) {
      return {
        type: 'click'
      };
    }
  };
});

// pulp.event.custom.js plugin "event:once" - fire an event once, then remove the handler 
// Event.observe(object, 'event:once', eventName, handler)
pulp.event.custom['event:once'] = {
    /** @ignore */
    add: function(node, eventOnce, type, handler) {
    return {
      type: type,
      originalHandler: handler,
      /** @ignore */
      handler: function(event) {
        pulp.event.stopObserving(node, type, handler);
        handler.call(node, event);
        return node;
      },
      capture: false
    };
  },
  /** @ignore */
  add: function(node, eventOnce, type, handler) {
    return {
      type: type,
      originalHandler: handler
    };
  }
};

// pulp.event.custom.js plugin "click:right" - fire on right-click
// Element.observe(node, 'click:right', handler)
pulp.event.custom['click:right'] = {
  /** @ignore */
  add: function(node, type, handler) {
    return {
      type: 'mousedown',
      /** @ignore */
      handler: function(event) {
        if (event.isRightClick()) {
          handler.call(node, event);
        }
        return node;
      }
    };
  },
  /** @ignore */
  remove: function(node, type, handler) {
    return {type: 'mousedown'};
  }
};

// pulp.event.custom.js plugin "click:middle" - fire on middle-click
// Element.observe(node, 'click:middle', handler)
pulp.event.custom['click:middle'] = {
  /** @ignore */
  add: function(node, type, handler) {
    return {
      type: 'mousedown',
      handler: function(event) {
        if (event.isMiddleClick()) {
          handler.call(node, event);
        }
        return node;
      }
    };
  },
  /** @ignore */
  remove: function(node, type, handler) {
    return {type: 'mousedown'};
  }
};

// pulp.event.custom.js plugin "key:idle" - fire when key has not been pressed within the element (window, form, form elements) for the given number of seconds
pulp.event.custom['key:idle'] = {
  /** @ignore */
  add: function(node, type, handler, seconds, resetFn) {
    var timeout, lastEvent = {};
    /** @ignore */
    var initTimeout = function() {
      timeout = window.setTimeout(function() {
        handler.call(node, lastEvent);
      }, seconds * 1000);
    };
    /** @ignore */
    var detector = function(event) {
      lastEvent = event;
      window.clearTimeout(timeout);
      if (resetFn) {
        resetFn.call(node, event);
      }
      initTimeout();
    };      
    initTimeout();
    return {
      type: 'keydown',
      handler: detector
    };      
  },
  /** @ignore */
  remove: function(node, type, handler) {
    return {type: 'keydown'};
  }
};

pulp.event.custom['mouse:idle'] = {
  /** @ignore */
  add: function(node, type, handler, seconds, resetFn) {
    var timeout, lastEvent = {};
    /** @ignore */
    var initTimeout = function() {
      timeout = window.setTimeout(function() {
        handler.call(node, lastEvent);
      }, seconds * 1000);
    };
    /** @ignore */
    var detector = function(event) {
      lastEvent = event;
      window.clearTimeout(timeout);
      if (resetFn) {
        resetFn.call(node, event);
      }
      initTimeout();
    };      
    initTimeout();
    return {
      type: 'mousemove',
      handler: detector
    };      
  },
  /** @ignore */
  remove: function(node, type, handler) {
    return {type: 'mousemove'};
  }
};

// TODO: add a browser test for contextmenu?
// pulp.event.custom.js plugin "click:contextmenu" - fire on right-click context menu
// Element.observe(node, 'click:contextmenu', handler)
pulp.event.custom['click:contextmenu'] = {
  /** @ignore */
  add: function(node, type, handler) {
    if ($e.isIE) {
      return {
        type: 'mousedown',
        /** @ignore */
        handler: function(event) {
          $e.stop(event);
          handler.call(node, event);
          return node;
        }
      }
    } else if ($e.isOpera) {
      return {
        type: 'ctrl+click',
        /** @ignore */
        handler: function(event) {
          $e.stop(event);
          handler.call(node, event);
          return node;
        }
      }
    } else {
      return {type: 'contextmenu'};
    }
  }
};

// pulp.event.custom.js plugin "key:combo" - fire on a given combination of keys (e.g. "CTRL+C")
(function($e) {  

  var extendedMethods = {
    getKeyString: function(event) {
      return $e._keyCodeToString(event.keyCode || event.charCode);
    }
  };
  $e.extend(extendedMethods).addMethods(extendedMethods);  
  
  pulp.base.extend($e, {
    _keysByCode: {
      221: ']',
      219: '[',
      109: '-',
      107: '=',
      220: '\\',
      222: "'",
      59: ';',
      188: ',',
      190: '.',
      191: '/',
      192: '`',
      16: 'SHIFT',
      17: 'CTRL',
      18: 'ALT',
      8: 'BACKSPACE',
      9: 'TAB',
      13: 'RETURN',
      27: 'ESC',
      37: 'LEFT',
      38: 'UP',
      39: 'RIGHT',
      40: 'DOWN',
      46: 'DEL',
      36: 'HOME',
      35: 'END',
      33: 'PAGEUP',
      34: 'PAGEDN',
      45: 'INS',
      91: 'WIN',
      20: 'CAPS'
    },
    
    _keyCodeToString: function(code) {
      if (code in $e._keysByCode) {
        return $e._keysByCode[code];
      }
      if ((code >= 65 && code <= 90) || (code >= 48 && code <= 57) || code == 32) {
        return String.fromCharCode(code);
      }
      return '';
    },
    
    _stringToKeyCode: function(string) {
      if (string in $e._codesByKey)
        return $e._codesByKey[string];
      return string.charCodeAt(0);
    },
    
    _keyDownDispatcher: function(event) {
      var code = event.keyCode || event.charCode;
      keysDown[code] = true;
      for (var key in special) {
        if (event[key + 'Key']) {
          keysDown[special[key]] = true;
        } else {
          delete keysDown[special[key]];
        }
      }
      
      var i = 0;
      for (key in keysDown) {
        i++;
      }
      if (i > 1) {
        var combo = [];
        i = 0;
        for (key in keysDown) {
          combo[i++] = $e._keyCodeToString(key);
        }
        combo = combo.sort();
        $e.fire(this, combo.join('+'), event);
      }
      
      // TODO: account for alt not always firing a key-press event (timeout?)  
    },
    
    _keyUpTracker: function(event) {
      var code = event.keyCode || event.charCode;
      delete keysDown[code];    
  //var combo = [];    
  //Object.keys(keysDown).each(function(c) {
//    if (keysDown[c]) combo.push(c);
  //});
  //var ev = combo.sort().collect(keyCodeToString).join('+');    
  //console.log('up: ' + ev + '(' + combo.join(' ') + ')');  
  //console.log('18: ' + (keysDown[18] ? 'alt is on' : 'alt is off'));  
    }    
    
  });
  
  
  var keysDown = {};
  var special = {meta: 'meta', shift: 16, ctrl: 17, alt: 18};

  $e._codesByKey = {};
  for (var c in $e._keysByCode) {
    $e._codesByKey[$e._keysByCode[c]] = c;
  }
  
  var keyComboObsCount = 0;

  /** @ignore */
  pulp.event.custom['key:combo'] = {
    add: function(node, type, combo, handler) {
      combo = combo.toUpperCase().split(/\s*\+\s* /).sort().join('+');
//console.log('registering key:combo ' + combo);      
      if (keyComboObsCount++ == 0) {
        $e.observe(node, 'keydown', $e._keyDownDispatcher);
        $e.observe(node, 'keyup', $e._keyUpTracker);
      }
      $e.observe(window, combo, handler);
//console.log(self.cache);      
      return true;
    },
    /** @ignore */
    remove: function(node, type, combo, handler) {
      combo = combo.toUpperCase().split(/\s*\+\s* /).sort().join('+');
      if (--keyComboObsCount == 0) {
        Element.stopObserving(node, 'keydown', $e._keyDownDispatcher);
        Element.stopObserving(node, 'keyup', $e._keyUpTracker);
      }
      $e.stopObserving(node, combo, handler);
      return true;
    }
  };
})(pulp.event);

// FireFox/Opera/Safari/KHTML have support for Mutation Events
pulp.event.custom['dom:updated'] = {
  /** @ignore */
  add: function(node, type, handler) {
    $e.observe(node, 'DOMAttrModified', handler);
    $e.observe(node, 'DOMNodeInserted', handler);
    $e.observe(node, 'DOMNodeRemoved', handler);
    return true;
  },
  /** @ignore */
  remove: function(node, type, handler) {
    $e.stopObserving(node, 'DOMAttrModified', handler);
    $e.stopObserving(node, 'DOMNodeInserted', handler);
    $e.stopObserving(node, 'DOMNodeRemoved', handler);
    return true;
  }  
  
};


/*
// pulp.event.custom.js plugin "mouse:drag" - fire on mousedown + mousemove
pulp.event.custom['mouse:drag'] = {
  add: function(el, ev, fn) {
    var boundFn = fn.bind(el);
    return [el, 'mousedown', function(event) {
      Element.fire(el, 'drag:start', event);
//      Element.observe(document, 'mouseover', function(event) {
//        Element.fire(event.element(), 'drop:enter');
//      });
//      Element.observe(document, 'mouseout', function(event) {
//        Element.fire(event.element(), 'drop:leave');
//      });    
      Element.observe(document, 'mousemove', boundFn);
      Element.observe(document, 'mouseup', function(event) {
        Element.fire(el, 'drag:end', event);
        // get drop:receive element from mouseover?
        // Element.fire(received, 'drop:receive');
        Element.stopObserving(document, 'mouseup', arguments.callee);
        Element.stopObserving(document, 'mousemove', boundFn);
      });
    }];
  },
  remove: function(el, ev, fn) {
    return [el, 'mousedown'];
  }
};



//    'dom:updated': null,
//    'dom:drag': null,
//    'dom:drop': null,
//    'dom:resize': null,
//    'activity:idle': null
//    key: null, //? ascii, universal keypress?
//    'mouse:gesture'

// activity:idle
(function() {  
  
  pulp.event.custom['activity:idle'] = {
    add: function(obs, el, fn, seconds, resetFn) {
      var hasKeydown = false;
      var hasMousemove = false;        
      var keyIdle = function() {
        hasKeydown = true;
        if (!hasMousemove)
          fn.apply(el);
      };
      var mouseIdle = function() {
        hasMousemove = true;
        if (!hasKeydown)
          fn.apply(el);
      };      
      var keyReset = function() { hasKeydown = true };
      var mouseReset = function() { hasMousemove = true };
      Element.observe(window, 'key:idle', keyIdle, seconds, keyReset);
      Element.observe(window, 'mouse:idle', mouseIdle, seconds, mouseReset);
      return false;
    },
    remove: function(obs, el, fn, seconds, resetFn) {
      // doh won't work!
      // Element.stopObserving(window, 'key:idle', keyIdle, seconds, keyReset);
      // Element.stopObserving(window, 'mouse:idle', mouseIdle, seconds, mouseReset);
    }
  };
  
})();*/


/**
* Event.simulate(@element, eventName[, options]) -> Element
*
* - @element: element to fire event on
* - eventName: name of event to fire (only MouseEvents and HTMLEvents interfaces are supported)
* - options: optional object to fine-tune event properties - pointerX, pointerY, ctrlKey, etc.
*
* $('foo').simulate('click'); // => fires "click" event on an element with id=foo
*
**/
/*
(function(){
  
  var eventMatchers = {
    'HTMLEvents': /^(?:load|unload|abort|error|select|change|submit|reset|focus|blur|resize|scroll)$/,
    'MouseEvents': /^(?:click|mouse(?:down|up|over|move|out))$/
  }
  var defaultOptions = {
    pointerX: 0,
    pointerY: 0,
    button: 0,
    ctrlKey: false,
    altKey: false,
    shiftKey: false,
    metaKey: false,
    bubbles: true,
    cancelable: true
  }
  
  Event.simulate = function(element, eventName) {
    var options = Object.extend(defaultOptions, arguments[2] || { });
    var oEvent, eventType = null;
    
    element = $(element);
    
    for (var name in eventMatchers) {
      if (eventMatchers[name].test(eventName)) { eventType = name; break; }
    }
 
    if (!eventType)
      throw new SyntaxError('Only HTMLEvents and MouseEvents interfaces are supported');
 
    if (document.createEvent) {
      oEvent = document.createEvent(eventType);
      if (eventType == 'HTMLEvents') {
        oEvent.initEvent(eventName, options.bubbles, options.cancelable);
      }
      else {
        oEvent.initMouseEvent(eventName, options.bubbles, options.cancelable, document.defaultView,
          options.button, options.pointerX, options.pointerY, options.pointerX, options.pointerY,
          options.ctrlKey, options.altKey, options.shiftKey, options.metaKey, options.button, element);
      }
      element.dispatchdispatchEvent(oEvent);
    }
    else {
      options.clientX = options.pointerX;
      options.clientY = options.pointerY;
      oEvent = Object.extend(document.createEventObject(), options);
      element.fireEvent('on' + eventName, oEvent);
    }
    return element;
  }
  
  //Element.addMethods({ simulate: Event.simulate });
})();
*/
