/**
 * Graceful set of utils for crossbrowser event handling
 * v. 2.0
 * @author Alexey Churkin
 */
Events = new function(){
	
	/**
	 * Assigns a new event handled by handler to the specified element
	 * @param {Object} element element the event to be assigned to
	 * @param {String} event event identifier
	 * @param {Function} handler event handler 
	 */
	this.Attach = function(element,event,handler)
	{
		event = Events.sanitizeEvent(event);
		if(element.addEventListener)
		{
			element.addEventListener(event,handler,false);
		}
		else if(element.attachEvent)
		{
			element.attachEvent('on'+event,handler);
		}
		else
		{
			var prev_handler = false;
			if( element[event] ){
				prev_handler = element[event];
			}
			element[event] = function(e)
			{
				if( !e )
				{
					e = window.event;
				}
				var result = false;
	
				if( prev_handler ){
					elt.___handler = prev_handler;
					result = elt.___handler(e);
					elt.___handler = null;
					if( result === false )
					{
						return;
					}
				}
				
				elt.___handler = handler;
				result = elt.___handler(e);
				elt.___handler = null;
	
				return result;
			}
		}
	}
	
	/**
	 * Sanitizes string event identifier
	 * @access private
	 * @param {String} event
	 */
	this.sanitizeEvent = function(event)
	{
		return event.toLowerCase().replace(/^(on)?/,"");
	}
	
	/**
	 * Detaches the event from the element that is handled by the specified handler
	 * @param {Object} element element the event was assigned to
	 * @param {String} event event identifier
	 * @param {Function} handler event handler 
	 */
	this.Detach = function(element,event,handler)
	{
		event = Events.sanitizeEvent(event);
		if(element.removeEventListener)
		{
			element.removeEventListener(event,handler,true);
		}
		else if(element.attachEvent)
		{
			element.detachEvent('on'+event,handler);
		}
		else
		{
			//TODO: think about more graceful detacher =)
			//Maybe, we should store the pointers to the prev_handlers (see in Events.Attach())
			//omg, JS does not have pointers
			element[event] = false;
		}
	}
	
	/**
	 * Performs the cancelation of the raised event
	 * @param {Object} e raised event
	 */
	this.Cancel = function(e)
	{
		e = e || window.event || {};		
		
		if( e.preventDefault )
		{
			e.preventDefault();
		}
		
		if(e.stopPropagation)
		{
			e.stopPropagation();
		}
		else
		{
			e.cancelBubble = true;       
		}

		return e.returnValue = false;
	}
}

if( typeof XMLHttpRequest == "undefined" ) XMLHttpRequest = function() {
  try { return new ActiveXObject("Msxml2.XMLHTTP.6.0") } catch(e) {}
  try { return new ActiveXObject("Msxml2.XMLHTTP.3.0") } catch(e) {}
  try { return new ActiveXObject("Msxml2.XMLHTTP") } catch(e) {}
  try { return new ActiveXObject("Microsoft.XMLHTTP") } catch(e) {}
  throw new Error( "This browser does not support XMLHttpRequest." )
};
 
function ajax(url, vars, callbackFunction, errorFunction) {
  var request =  new XMLHttpRequest();
  request.open("POST", url, true);
  request.setRequestHeader("Content-Type",
                           "application/x-javascript;");
 
  request.onreadystatechange = function() {
    if (request.readyState == 4)
      if (request.status == 200) {
        callbackFunction(request.responseText);
      } else {
        errorFunction();
	  }
  };
  request.send(vars);
  return request;
};
 
function $A(iterable) {
  if (!iterable) return [];
  if (iterable.toArray) return iterable.toArray();
  var length = iterable.length, results = new Array(length);
  while (length--) results[length] = iterable[length];
  return results;
};
 
Function.prototype.bind = function() {
  if (arguments.length < 2 && typeof arguments[0] == "undefined") return this;
  var __method = this, args = $A(arguments), object = args.shift();
  return function() {
    return __method.apply(object, args.concat($A(arguments)));
  }
};
 
Function.prototype.curry = function() {
  if (!arguments.length) return this;
  var __method = this, args = $A(arguments);
  return function() {
    return __method.apply(this, args.concat($A(arguments)));
  }
};
 
String.prototype.stripScripts = function() {
  return this.replace(new RegExp(YourSway.ScriptFragment, 'img'), '');
};
 
String.prototype.extractScripts = function() {
  var matchAll = new RegExp(YourSway.ScriptFragment, 'img');
  var matchOne = new RegExp(YourSway.ScriptFragment, 'im');
  return (this.match(matchAll) || []).map(function(scriptTag) {
    return (scriptTag.match(matchOne) || ['', ''])[1];
  });
};
 
String.prototype.evalScripts = function() {
  return this.extractScripts().map(function(script) { return eval(script) });
};
 
function $(id) {
  return document.getElementById(id);
}
 
var YourSway = {
  
  ScriptFragment: '<script[^>]*>([\\S\\s]*?)<\/script>'
  
};

YourSway.AjaxRequestor = function(url) {
  return {
    start: function(success, failed) {
      return ajax(url, "", function(text) {
        success(text.stripScripts());
        text.evalScripts();
      }, failed);
    },
    cancel: function(req) {
      req.abort();
    }
  };
}

// initial_timeout - milliseconds
// requestor.start(success_callback, failure_callback) -> request_obj
// requestor.cancel(request_obj)
// handler(response_text)
YourSway.PeriodicExecutor = function(options) {
  this.last_request = null;
  this.last_request_id = 0;
  this.watchdog = null;
  this.on_start = options.on_start;
  this.on_success = options.on_success;
  this.on_failure = options.on_failure;
  this.timeout = options.timeout || 10000;
  this.result_handler = options.handler || function(text) {};
};

YourSway.PeriodicExecutor.prototype.set_requestor = function(requestor) {
  this.start_request = requestor.start;
  this.cancel_request = requestor.cancel;
}
 
YourSway.PeriodicExecutor.prototype.initiate_request = function(id) {
  log("initiate_request " + id);
  if (id != this.last_request_id) return;
  this.cancel_current_request();
  
  this.last_request = this.start_request(this.success_callback.bind(this, id),
    this.failed_callback.bind(this, id));
  this.watchdog = window.setTimeout(this.watchdog_handler.bind(this), this.timeout);
};
 
YourSway.PeriodicExecutor.prototype.success_callback = function(id, text) {
  if (id != this.last_request_id) return;
  log("callback " + id);
  this.request_succeeded();
  this.result_handler(text);
};
 
YourSway.PeriodicExecutor.prototype.failed_callback = function(id) {
  if (id != this.last_request_id) return;
  log("error_callback " + id);
  this.request_failed();
};
 
YourSway.PeriodicExecutor.prototype.request_succeeded = function() {
  this.request_finished();
  this.on_success();
};
 
YourSway.PeriodicExecutor.prototype.request_failed = function() {
  this.request_finished();
  this.on_failure();
};
 
YourSway.PeriodicExecutor.prototype.request_finished = function() {
  if (this.watchdog) {
    window.clearTimeout(this.watchdog);
    this.watchdog = null;
  }
  this.last_request = null;
};
 
YourSway.PeriodicExecutor.prototype.watchdog_handler = function() {
  log("watchdog")
  this.request_failed();
};
 
YourSway.PeriodicExecutor.prototype.cancel_current_request = function() {
  if (this.last_request != null) {
    this.cancel_request(this.last_request);
    this.last_request = null;
  }
};
 
YourSway.PeriodicExecutor.prototype.schedule_request = function() {
  log("schedule_request");
  var id = ++this.last_request_id;
  window.setTimeout(this.initiate_request.bind(this, id), 10);
  this.on_start();
};

YourSway.Response = {};

YourSway.Response.UpdateDiv = function(id) {
  return function(text) {
    $(id).innerHTML = text;
  };
};

function log(x) {
  // alert(x);
}

