/* eventManager.js, WAJAF, the WebAbility(r) Javascript Application Framework
 (c) 2008 Philippe Thomassigny
 */

function _eventManager() {
	var self = this;
	this.listenerid = 1;
	this.functionid = 1;
	this.events = {};
	this.flushs = [];
	this.keys = [];
	this.specialkeys = {
		'esc' :27,
		'escape' :27,
		'tab' :9,
		'space' :32,
		'return' :13,
		'enter' :13,
		'backspace' :8,
		'scrolllock' :145,
		'capslock' :20,
		'numlock' :144,
		'pause' :19,
		'break' :19,
		'insert' :45,
		'home' :36,
		'delete' :46,
		'end' :35,
		'pageup' :33,
		'pagedown' :34,
		'left' :37,
		'up' :38,
		'right' :39,
		'down' :40,
		'f1' :112,
		'f2' :113,
		'f3' :114,
		'f4' :115,
		'f5' :116,
		'f6' :117,
		'f7' :118,
		'f8' :119,
		'f9' :120,
		'f10' :121,
		'f11' :122,
		'f12' :123
	};

	// eventname: one of: mousedown, mouseup, click, dblclick, mousemove,
	// mouseover, mouseout, mousewheel,
	// keydown, keyup, keypress, load, unload, scroll,
	// focus, blur, change, submit, abort, error, reset, resize
	// eventnode: the id or the node itself
	// eventfunction: pointer to the function to execute when the event happens
	// eventcapture: true/false to notify if this event is greedy
	this.addListener = this.on = this.add = this.start = this.listen = this.attachEvent = this.registerEvent = addListener;
	function addListener(eventname, eventnode, eventfunction, eventcapture) {

		if (typeof eventnode == 'string')
			eventnode = $(eventnode);
		if (!eventnode) // no node found ?
			return false;

		// link the UID to the node
		if (eventnode.listeneruid == undefined)
			eventnode.listeneruid = self.listenerid++;
		if (eventfunction.functionuid == undefined)
			eventfunction.functionuid = self.functionid++;

		if (self.events[eventnode.listeneruid] == undefined)
			self.events[eventnode.listeneruid] = {};
		if (self.events[eventnode.listeneruid][eventname] == undefined)
			self.events[eventnode.listeneruid][eventname] = {};

		// get the context from the ID of the node if any
		eventnode.context = WA.context;

		thefunction = function() {
			var xid = oldcontext = null;
			if (this.id && this.id.indexOf('-') != -1) {
				xid = WA.parseID(this.id);
				oldcontext = WA.setContext(xid[0] + '-' + xid[1] + '-');
			}
			var ret = eventfunction.apply(this, arguments);
			if (xid)
				WA.setContext(oldcontext);
			return ret;
		}

		if (eventname == 'load' && browser.msie) // special incompatible IE
													// not firing onload event
		{
			thefunction = function() {
				if (this.readyState != 'complete'
						&& this.readyState != 'loaded')
					return null;
				var xid = oldcontext = null;
				if (this.id && this.id.indexOf('-') != -1) {
					xid = WA.parseID(this.id);
					oldcontext = WA.setContext(xid[0] + '-' + xid[1] + '-');
				}
				var ret = eventfunction.apply(this, arguments);
				if (xid)
					WA.setContext(oldcontext);
				return ret;
			};
			eventnode.onreadystatechange = thefunction;
		} else if (eventnode.addEventListener) {
			if (eventname == 'mousewheel') // special incompatible mousewheel
			{
				eventnode.addEventListener('DOMMouseScroll', thefunction,
						eventcapture);
			}
			eventnode.addEventListener(eventname, thefunction, eventcapture);
		} else if (eventnode.attachEvent) {
			eventnode.attachEvent('on' + eventname, thefunction);
		} else {
			eventnode['on' + eventname] = thefunction;
		}
		self.events[eventnode.listeneruid][eventname][eventfunction.functionuid] = thefunction;
		return true;
	}

	// must be the SAME PARAMETERS as addListener
	this.removeListener = this.off = this.remove = this.stop = this.detachEvent = removeListener;
	function removeListener(eventname, eventnode, eventfunction, eventcapture) {
		if (typeof eventnode == 'string')
			eventnode = $(eventnode);
		if (!eventnode) // no node found ?
			return;
		if (eventnode.listeneruid == undefined) // node not registered here
			return;
		if (eventfunction.functionuid == undefined) // function not registered
													// here
			return;
		if (self.events[eventnode.listeneruid] == undefined) // already
																// unregistered
																// ?
			return;
		if (self.events[eventnode.listeneruid][eventname] == undefined) // already
																		// unregistered
																		// ?
			return;
		if (self.events[eventnode.listeneruid][eventname][eventfunction.functionuid] == undefined) // already
																									// unregistered
																									// ?
			return;

		if (eventname == 'load' && browser.msie) // special incompatible IE
													// not firing onload event
		{
			eventnode.onreadystatechange = nothing;
		} else if (eventnode.removeEventListener) {
			if (eventname == 'mousewheel') {
				eventnode
						.removeEventListener(
								'DOMMouseScroll',
								self.events[eventnode.listeneruid][eventname][eventfunction.functionuid],
								eventcapture);
			}
			eventnode
					.removeEventListener(
							eventname,
							self.events[eventnode.listeneruid][eventname][eventfunction.functionuid],
							eventcapture);
		} else if (eventnode.detachEvent) {
			eventnode
					.detachEvent(
							'on' + eventname,
							self.events[eventnode.listeneruid][eventname][eventfunction.functionuid]);
		} else {
			eventnode['on' + eventname] = null;
		}
		delete self.events[eventnode.listeneruid][eventname][eventfunction.functionuid];
		// *********************************************
		// do we clean the 3 levels of the array ?
		return;
	}

	// key is 'modif[+modif]+key'
	// modif is 'shift', 'alt', 'control' or 'ctrl'
	// key is 0-9, a-z, !@#$%^&*()_-+=}{]["';?><,./`~
	// can be also: special keys, arrows, functions etc. see the array in the
	// function.

	this.keyListener = keyListener;
	function keyListener(key, callback, node, bubble) {
		var xkey = key.toLowerCase().split("+");
		if (node == undefined)
			node = document;
		if (bubble == undefined)
			bubble = false;
		for ( var i in xkey) {
			if (xkey[i] == 'shift' || xkey[i] == 'control' || xkey[i] == 'ctrl'
					|| xkey[i] == 'alt')
				continue;
			if (self.specialkeys[xkey[i]] != undefined)
				continue;
			// should be normal char, we take the 1rst one to be sure
			xkey[i] = xkey[i].charAt(0);
		}

		var data = {
			skey :key,
			key :xkey,
			callback :callback,
			node :node,
			bubble :bubble
		};
		self.keys.push(data);
		return;
	}

	this.keycallback = keycallback;
	function keycallback(e)
	{
		var code = browser.getKey(e);
		var c = String.fromCharCode(code).toLowerCase();
		var shift = browser.ifShift(e);
		var ctrl = browser.ifCtrl(e);
		var alt = browser.ifAlt(e);
		for ( var i in self.keys)
		{
		  if (typeof self.keys[i] == 'function') // protection IE bug
        continue;
		  if (typeof self.keys[i]['key'] == 'function')
        continue;
			// check any keys combination if ok
			var isok = 0;
			for ( var j in self.keys[i]['key']) {
  		  if (typeof self.keys[i]['key'][j] == 'function') // protection IE bug
          continue;
				if (self.keys[i]['key'][j] == 'shift' && shift)
					isok++;
				else if (self.keys[i]['key'][j] == 'alt' && alt)
					isok++;
				else if ((self.keys[i]['key'][j] == 'control' || self.keys[i]['key'][j] == 'ctrl')
						&& ctrl)
					isok++;
				else if (self.specialkeys[self.keys[i]['key'][j]] == code)
					isok++;
				else if (self.keys[i]['key'][j] === c)
					isok++;
			}
			if (isok == self.keys[i]['key'].length) {
				self.keys[i]['callback'](e, self.keys[i]['skey']);
			}
		}
		return;
	}

	this.registerFlush = registerFlush;
	function registerFlush(functionflush) {
		self.flushs.push(functionflush);
		return;
	}

	this.flush = flush;
	function flush() {
		return;
		for (i in self.events) {
			for (j in self.events[i]) {
				for (k in self.events[i][j]) {
					if (self.events[i][j][k][0]) {
						if (j == 'mousewheel') {
							self.events[i][j][k][0].removeEventListener(
									'DOMMouseScroll', self.events[i][j][k][2],
									self.events[i][j][k][3]);
						}
						self.events[i][j][k][0].removeEventListener(j,
								self.events[i][j][k][2],
								self.events[i][j][k][3]);
					} else if (self.events[i][j][k][0].detachEvent) {
						self.events[i][j][k][0].detachEvent('on' + j,
								self.events[i][j][k][2]);
					} else {
						self.events[i][j][k][0]['on' + j] = null;
					}
				}
			}
		}

		// then call all flush for other managers
		for ( var i = 0, l = self.flushs.length; i < l; i++) {
			self.flushs[i]();
			self.flushs[i] = null;
		}

		// we stop listening unload and keypress

		delete self.events;
		delete self.flushs;
		delete self.keys;
		self = null;
		return;
	}

	// we take control of unload
	this.addListener('unload', window, this.flush, false);
	// we listen the key binder
	this.addListener('keydown', document, this.keycallback, false);
}

var eventManager = new _eventManager();
// rapid shortcuts
var on = eventManager.addListener;
var off = eventManager.removeListener;
var key = eventManager.keyListener;

/* ajaxManager.js, WAJAF, the WebAbility(r) Javascript Application Framework
   (c) 2008 Philippe Thomassigny
*/

function ajaxRequest(url, method, data, feedback, autosend, listener)
{
  var self = this;
  // parameters
  this.url = url;
  this.method = method.toUpperCase();
  this.data = data;
  this.feedback = feedback;
  this.autosend = autosend;
  // special parameters
  this.period = 0;
  this.times = 0;
  this.timeoutabort = 0;        // time out to abort, no default, let it to the ajax autocontrol.
  this.statefeedback = null;    // consider waiting, error and abort feedbacks
  // working attributes
  this.request = null;
  this.parameters = null;
  this.timer = null;
  this.timerabort = null;
  this.state = 0;               // 0 = nothing, 1 = sent and waiting, 2 = finished, 3 = error
  this.listener = listener;


  try { this.request = new XMLHttpRequest(); }
  catch(e) {
    try { this.request = new ActiveXObject("Msxml2.XMLHTTP.3.0"); }
    catch(e) {
      try { this.request = new ActiveXObject("Msxml2.XMLHTTP"); }
      catch(e) {
        try { this.request = new ActiveXObject("Microsoft.XMLHTTP"); }
        catch(e) {
          alert("XMLHttpRequest not supported");
          return;
        }
      }
    }
  }

  this.callNotify = callNotify;
  function callNotify(event)
  {
    if (self.listener)
    {
      self.listener(event);
    }
  }

  // Special parameters
  this.setPeriodic = setPeriodic;
  function setPeriodic(period, times)
  {
    self.period = period;
    self.times = times;
    return;
  }

  this.addStateFeedback = addStateFeedback;
  function addStateFeedback(statefeedback, timeoutabort)
  {
    self.statefeedback = statefeedback;
    self.timeoutabort = timeoutabort;
    return;
  }

  // Parameters for POST/GET send
  this.addParameter = addParameter;
  function addParameter(id, value)
  {
    if (self.parameters === null)
      self.parameters = {};
    self.parameters[id] = value;
    return;
  }

  this.getParameters = getParameters;
  function getParameters()
  {
    var data = self.data || '';
    for (i in self.parameters)
      data += (data.length > 0?'&':'') + escape(i) + '=' + escape(self.parameters[i]);
    return data;
  }

  this.clearParameters = clearParameters;
  function clearParameters()
  {
    self.parameters = null;
    return;
  }

  // Ajax control
  this.headers = headers;
  function headers()
  {
    self.request.setRequestHeader('X-Requested-With', 'WAJAF::Ajax - WebAbility(r) v4');
    if (self.method == 'POST')
    {
      self.request.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
      if (self.request.overrideMimeType)
        self.request.setRequestHeader("Connection", "close");
      self.request.setRequestHeader("Method", "POST " + self.url + " HTTP/1.1");
    }
    return;
  }

  this.send = send;
  function send()
  {
    if (self.timer)
      self.timer = null;
    if (self.request.readyState != 0 && self.request.readyState != 4) // still doing something
      return;

    self.request.onreadystatechange = self.process;
    if (self.timeoutabort)
      self.timerabort = setTimeout( function() { self.abort(); }, self.timeoutabort );
    try
    {
      var url = self.url;
      var parameters = self.getParameters();
      if (self.method == 'GET' && parameters.length > 0)
        url += (url.match(/\?/) ? '&' : '?') + parameters;
      self.request.open(self.method, url, true);
      self.headers();
      self.callNotify('start');
      self.request.send(self.method == 'POST' ? parameters : null);
      self.state = 1;
      debug('Enviando request AJAX: '+url, 2);
    }
    catch (e)
    {
      debug('Error creando request AJAX: '+url, 2);
      self.state = 3;
      self.processError(1, e);
    }
    return;
  }

  this.process = process;
  function process()
  {
    try
    {
      if (self.request.readyState == 4)
      {
        if (self.request.status == 200)
        {
          debug('Respuesta AJAX recibida: '+self.url, 2);
          if (self.timerabort)
          {
            window.clearTimeOut(self.timerabort);
            self.timerabort = null;
          }
          self.callNotify('stop');
          if (self.feedback)
          {
            self.feedback(self.request);
          }
          self.state = 2;
        }
        else
        {
          debug('Error en la respuesta AJAX recibida: '+self.url, 2);
          self.state = 3;
          // we call error feedback, or alert
          self.processError(3, "Error "+self.request.status+":\n" + self.request.statusText);
        }
        self.request.onreadystatechange = nothing;  // IE6 CANNOT assign null !!!
        var state = self.checkPeriod();
        if (!state)
          setTimeout( function() { ajaxManager.destroyRequest(self); }, 1);
      }
      else
      {
        self.waiting();
      }
    }
    catch(e)
    {
      debug('Error grave en la respuesta AJAX recibida: '+self.url, 2);
      self.state = 3;
      self.processError(2, e);
    }
    return;
  }

  this.checkPeriod = checkPeriod;
  function checkPeriod()
  {
    if (self.period)
    {
      if (self.times-- > 0)
      {
        self.timer = setTimeout( function() { self.send(); }, self.period);
        return true;
      }
    }
    return false;
  }

  this.waiting = waiting;
  function waiting()
  {
    // dispatcher for user events like "loading...", "making request", "sending information" based on readyState , etc ?
    // could also use a setInterval to periodically call this function to know how is going the call
    if (self.statefeedback)
      self.statefeedback('wait', self.request.readyState, '');
    return;
  }

  // any error
  // type = 1: error sending, 2: error during process, 3: error state != 200, 4: timeout forced
  this.processError = processError;
  function processError(type, error)
  {
    self.callNotify('error');
    if (typeof error == 'object')
      error = error.message;
    // abort and call feedback error
    if (self.statefeedback)
      self.statefeedback('error', type, error);
    //else
      //alert('Error: '+type+', '+error);
    return;
  }

  // we abort after a given timeout
  this.abort = abort;
  function abort()
  {
    self.timerabort = null;
    if (self.timer)
    {
      window.clearTimeOut(self.timer);
      self.timer = null;
    }
    self.processError(4, 'Timeout');
    self.request.abort();
    self.request.onreadystatechange = null;
    if (!self.checkPeriod())
      setTimeout( function() { ajaxManager.destroyRequest(self); }, 1);
    return;
  }

  this.destroy = destroy;
  function destroy()
  {
    if (self.timerabort)
    {
      window.clearTimeOut(self.timerabort);
      self.timerabort = null;
    }
    if (self.timer)
    {
      window.clearTimeOut(self.timer);
      self.timer = null;
    }
    if (self.state == 1 || self.state == 3)
    {
      self.abort();
    }
    self.request.onreadystatechange = nothing;
    self.clearParameters();
    delete self.request;
    self.statefeedback = null;
    self.feedback = null;
    self = null;
    return;
  }

  if (autosend)
    self.send();
  return;
}

function _ajaxManager()
{
  var self = this;
  this.requests = [];
  this.listener = null;

  this.setListener = setListener;
  function setListener(listener)
  {
    self.listener = listener;
  }

  this.callNotify = callNotify;
  function callNotify(event)
  {
    if (self.listener)
    {
      self.listener(event);
    }
  }

  this.destroyRequest = destroyRequest;
  function destroyRequest(r)
  {
    for (var i=0, l=self.requests.length; i < l; i++)
    {
      if (self.requests[i] == r)
      {
        self.requests[i].destroy();
        self.requests.splice(i, 1);
        self.callNotify('destroy');
        break;
      }
    }
    return;
  }

  this.createRequest = createRequest;
  function createRequest(url, method, data, feedback, dosend)
  {
    self.callNotify('create');
    var r = new ajaxRequest(url, method, data, feedback, dosend, self.listener);
    if (r)
    {
      self.requests.push(r);
    }
    return r;
  }

  this.createPeriodicRequest = createPeriodicRequest;
  function createPeriodicRequest(period, times, url, method, data, feedback, dosend)
  {
    self.callNotify('create');
    var r = new ajaxRequest(url, method, data, feedback, dosend, self.listener);
    if (r)
    {
      self.requests.push(r);
      r.setPeriodic(period, times);
    }
    return r;
  }

  this.destroy = destroy;
  function destroy()
  {
    self.listener = null;
    for (var i=0, l=self.requests.length; i < l; i++)
      self.requests[i].destroy();
    delete self.requests;
    self = null;
    return;
  }

  eventManager.registerFlush(self.destroy);
  return;
}

var ajaxManager = new _ajaxManager();
// shortcuts
var ajax = ajaxManager.createRequest;
