/* ----- TEvent ----- */

/**
 * TEvent is used to create custom events for
 * TEventDispatcher instances
 *
 * @param {String} type: event type
 * @param {Object} target: the event target
 */
var TEvent = function (type, target) {
  this.type = type;                       // Event Type
  this.target = target;                   // Event Target, component that fired the event
  this.currentTarget = target;            // Current Target, current event component in event propagation
};
TEvent.prototype.free = function() { };
TEvent.prototype.stopPropagation = function() { };
TEvent.prototype.preventDefault = function() { };

/* ----- TDOMEvent ----- */

/**
 * TDOMEvent is used to manage DOM Elements events
 *
 * @param {Event} event: TEvent or DOM Event
 * @param {Object} target: event target
 */
TDOMEvent = function (e, target) {
  TEvent.prototype.constructor.call(this, e.type, target);
  this.event = e;

  // Resolve Event Target
  target = e.target || e.srcElement;
  this.target = TDOM.resolveTextNode(target);

  // Resolve Related Target
  this.relatedTarget = e.relatedTarget;
  if (!this.relatedTarget) {
    if (this.type == "mouseover") {
      this.relatedTarget = e.fromElement;
    } else
    if (this.type == "mouseout") {
      this.relatedTarget = e.toElement;
    }
  }

  // Retrieve keyboard information
  this.keyCode = e.keyCode || e.which || 0;
  this.charCode = e.charCode || (this.type == "keypress" ? e.keyCode : 0);
  this.ctrlKey = e.ctrlKey;
  this.altKey = e.altKey;
  this.shiftKey = e.shiftKey;
  this.metaKey = e.metaKey;

  // Retrieve mouse information
  this.screenX = e.screenX || 0;
  this.screenY = e.screenY || 0;
  this.offsetX = typeof e.layerX  != "undefined" ? e.layerX  : e.offsetX;
  this.offsetY = typeof e.layerY  != "undefined" ? e.layerY  : e.offsetY;
  this.clientX = typeof e.clientX != "undefined" ? e.clientX : e.pageX;
  this.clientY = typeof e.clientY != "undefined" ? e.clientY : e.pageY;
  this.buttons = TUserAgent.IE ? { left: e.button & 1, middle: e.button & 4, right: e.button & 2 } : { left: e.button == 0, middle: e.button == 1, right: e.which ? e.which == 3 : e.button == 2 };
};
TDOMEvent.inherits(TEvent);

/**
 * Stop event propagation (bubbling)
 */
TDOMEvent.prototype.stopPropagation = function () {
  if(this.event.stopPropagation){
    this.event.stopPropagation();
  }else{
    this.event.cancelBubble = true;
  }
};

/**
 * Prevent default event behavior
 */
TDOMEvent.prototype.preventDefault = function () {
  if(this.event.preventDefault){
    this.event.preventDefault();
  }else{
    this.event.returnValue = false;
  }
};

/* ----- TEventDispatcher ----- */

/**
 * Base class for any EventDispatcher
 * Extend this class if your object need to dispatch events
 */
var TEventDispatcher = function () { };
TEventDispatcher.prototype.isDispatcher = true;
TEventDispatcher.prototype.addEventListener = function (type, callback, scope) {
  TEvents.listen(this, type, callback, scope);
};
TEventDispatcher.prototype.removeEventListener = function (type, callback, scope) {
  TEvents.unlisten(this, type, callback, scope);
};
TEventDispatcher.prototype.dispatch = function (type) {
  var args = Array.prototype.slice.call(arguments,0);
  args.unshift(this);
  return TEvents.dispatch.apply(this, args);
};
TEventDispatcher.prototype.free = function () {
  TEvents.unlistenAll(this);
};

/* ----- TEvents ----- */

/**
 * TEvents classis a set of static methods to allow
 * listeners management.
 */
var TEvents = function() {};
TEvents.nextIndex = 1;        // Autogenerate index to bind EventDispatchers and listeners
TEvents.listeners = {};       // List of listeners, stored by object index and event type
TEvents._items = [];

/**
 * Generate an unique ID to bind event to an object or DOM Element.
 * If object does already have an index, then return it
 * Event index is used to store events listeners in the listeners array
 * and easily retrieve it later.
 *
 * @param {Object | DOM Element} obj: object to get key
 */
TEvents.getIndex = function (obj) {
  var prop = "dispatcher_unique_index";
  if (!obj) { return; }
  if (obj.hasOwnProperty && obj.hasOwnProperty(prop)) {
    return obj[prop];
  }
  if (!obj[prop]) {
    obj[prop] = 'e' + TEvents.nextIndex++;
  }
  return obj[prop];
};

/**
 * Return a listener from the list that meet parameters
 *
 * @param {EventDispatcher | DOM Element | String} src: source of event
 * @param {String} type: events type
 * @param {Function} func: callback function
 * @param {Object} scope: scope for the callback function
 * @return {Listener} return listener if found
 */
TEvents.getListener = function (src, type, callback, scope) {
  src = TDOM.getElement(src);
  var id = TEvents.getIndex(src);
  var list = TEvents.listeners;
  if (list[id] && list[id][type]) {
    list = list[id][type];
    for (var i = 0; i < list.length; i++) {
      if (list[i].callback == callback && list[i].scope == scope) {
        return list[i];
      }
    }
  }
  return null;
};

/**
 * Add a new listener to an event for an EventDispatcher or DOM Element
 *
 * @param {EventDispatcher | DOM Element | String} src: source of event
 * @param {String} type: events type
 * @param {Function} func: callback function
 * @param {Object} scope: (Optional) scope for the callback function
 * @return {Boolean} true or false if listener successfully added
 */
TEvents.listen = function (src, type, callback, scope) {
  src = TDOM.getElement(src);
  if (!src || !callback || !callback.call) { return false; }
  scope = scope ? scope : src;
  // Already Exists?
  var l = TEvents.getListener(src, type, callback, scope);
  if (!l) {
    // Get listeners of type 'type' for src object if any
    var id = TEvents.getIndex(src);
    var list = TEvents.listeners;
    list[id] = list[id] || {};
    list[id][type] = list[id][type] || [];
    list = list[id][type];
    // Create listener and callback wrapper
    l = {src:src, type:type, callback:callback, scope:scope};
    var wrapper = function (e) { return TEvents.fireListener(l, e); };
    l.wrapper = wrapper;
    list.push(l);
    // Add DOM/IE event listener
    if (src.addEventListener) {
      // Only on DOM Elements, avoid EventDispatcher recursion
      if (!src.isDispatcher) {
        src.addEventListener(type, wrapper, false);
      }
    } else
    if (src.attachEvent) {
      src.attachEvent("on" + type, wrapper);
    } else {
      return false; // Events no supported
    }
  }
  return true;
};

/**
 * Remove a listener from the listeners stack and remove any
 * DOM listener associated.
 *
 * @param {EventDispatcher | DOM Element | String} src: source of event
 * @param {String} type: events type
 * @param {Function} func: callback function
 * @param {Object} scope: scope for the callback function
 * @return {Boolean} true is listener has been successfully removed
 */
TEvents.unlisten = function(src, type, callback, scope) {
  src = TDOM.getElement(src);
  var l = null;
  var id = TEvents.getIndex(src);
  var list = TEvents.listeners;
  scope = scope ? scope : src;
  // Get listener and remove it from the list
  if (list[id] && list[id][type]) {
    list = list[id][type];
    for (var i = 0; i < list.length; i++) {
      if (list[i].callback == callback && list[i].scope == scope) {
        l = list[i];
        list.splice(i, 1);
        break;
      }
    }
  }
  if (l) {
    if (src.removeEventListener) {
      if (!src.isDispatcher) {
        src.removeEventListener(type, l.wrapper, false);
      }
    } else
    if (src.detachEvent) {
      src.detachEvent("on" + type, l.wrapper);
    }
  }
};

/**
 * Remove a listener of an object and, if present, for an event type
 * @param {EventDispatcher | DOM Element | String} src: source of event
 */
TEvents.unlistenAll = function(src) {
  src = TDOM.getElement(src);
  var id = TEvents.getIndex(src);
  if (TEvents.listeners[id]) {
    for (var type in TEvents.listeners[id]) {
      TEvents.unlistenType(src, type);
    }
    delete TEvents.listeners[id];
  }
};

/**
 * Remove a listener of an object for an event type
 *
 * @param {EventDispatcher | DOM Element | String} src: source of event
 * @param {String} type: (Optional) type: events type
 */
TEvents.unlistenType = function (src, type) {
  src = TDOM.getElement(src);
  var id = TEvents.getIndex(src);
  if (TEvents.listeners[id] && TEvents.listeners[id][type]) {
    var l = TEvents.listeners[id][type][0];
    while (l) {
      TEvents.unlisten(l.src, l.type, l.callback, l.scope);
      l = TEvents.listeners[id][type][0];
    }
    delete TEvents.listeners[id][type];
  }
};

/**
 * Fire an event listener callback function
 *
 * @param {Listener} l: listener object
 * @param {Event} e: TEvent or DOMEvent
 * @result {Boolean} callback result
 */
TEvents.fireListener = function(l, e) {
  if (!l) { return true; }
  e = e || window.event;
  var event = new TDOMEvent(e, l.src);
  try {
    return l.callback.call(l.scope, event) !== false;
  } finally {
    event.event = null;
  }
};

/**
 * Dispatch an event
 * @param {Object} src: event dispatcher
 * @param {String} e: event type
 */
TEvents.dispatch = function (src, e) {
  src = TDOM.getElement(src);
  if (!src) { return; }
  e = new TEvent(e, src);
  var id = TEvents.getIndex(src);
  var result = true;
  if (TEvents.listeners[id] && TEvents.listeners[id][e.type]) {
    var list = TEvents.listeners[id][e.type];
    var args = Array.prototype.slice.call(arguments,2);
    args.unshift(e);
    for (var i = 0; i < list.length; i++) {
      result = result && list[i].callback.apply(list[i].scope, args) !== false;
    }
  }
  return result;
};

