var Ajax = new Class(
{
   Implements: [Events, Options],

   options:
   {
      data: '',
      url: window.location.href,
      cdurl: window.location.href,
      headers: {'X-Requested-With': 'XMLHttpRequest', 'Accept': 'text/javascript, text/html, application/xml, text/xml, */*'},
      async: true,
      method: 'post',
      urlEncoded: true,
      charset: 'utf-8',
      isShowLoader: true,
      onShowLoader: null,
      onHideLoader: null,
      onHistory: null
   },

   initialize: function(options)
   {
      this.setOptions(options);
      this.currentHash = null;
      this.historyInterval = null;
      this.headers = new Hash(this.options.headers);
      this.running = false;
      this.process = 0;
   },

   send: function(options)
   {
      this.running = true;
      this.process++;
      this.showLoader();
      var type = $type(options);
      if (type == 'string' || type == 'element') options = {data: options};
      var old = this.options;
      options = $extend({data: old.data, url: old.url, method: old.method}, options);
      var data = options.data, url = options.url, method = options.method;
      if (this.options.urlEncoded && method == 'post')
      {
         var charset = (this.options.charset) ? '; charset=' + this.options.charset : '';
         this.headers.set('Content-type', 'application/x-www-form-urlencoded' + charset);
      }
      if (data && method == 'get')
      {
         url = url + (url.contains('?') ? '&' : '?') + data;
         data = null;
      }
      var xhr = new Browser.Request();
      xhr.open(method.toUpperCase(), url, this.options.async);
      xhr.onreadystatechange = function()
      {
         if (xhr.readyState != 4) return;
         if (xhr.status >= 200 && xhr.status < 300) {this.process--; $exec(xhr.responseText); this.fireEvent('complete', xhr);}
         else this.fireEvent('failure', xhr);
         xhr.onreadystatechange = $empty;
         if (this.process < 1) {this.running = false; this.process = 0; this.hideLoader()}
      }.bind(this);
      this.headers.each(function(value, key)
      {
         if (!$try(function(){xhr.setRequestHeader(key, value); return true;}.bind(this)))
         this.fireEvent('exception', [key, value]);
      }, this);
      xhr.send(data);
      return xhr;
   },

   abort: function(xhr)
   {
      if (!xhr) return;
      xhr.abort();
      xhr.onreadystatechange = $empty;
      this.process--;
      if (this.process < 1) {this.running = false; this.process = 0; this.hideLoader()}
   },

   doit: function(func)
   {
      var args = new Array();
      for (var i = 1; i < arguments.length; i++) args[i] = arguments[i];
      return this.call(func, $(document.body).getFormValues(), args);
   },

   call: function(func)
   {
      var params = "", i = 1, args = arguments;
      if (arguments.length == 2 && typeof(arguments[1]) == 'object')
      {
         args = arguments[1];
         i = 0;
      }
      params += "ajaxfunc=" + encodeURIComponent(func);
      for (; i < args.length; i++) params += "&ajaxargs[]=" + encodeURIComponent(this.encodeObj(args[i]));
      return this.send(params);
   },

   cdcall: function(func)
   {
      var params = '', i = 1, args = arguments;
      if (arguments.length == 2 && typeof(arguments[1]) == 'object')
      {
         args = arguments[1];
         i = 0;
      }
      if (this.options.cdurl.charAt(this.options.cdurl.length - 1) != '?') params = '?';
      params += "ajaxfunc=" + encodeURIComponent(func);
      for (; i < args.length; i++) params += "&ajaxargs[]=" + encodeURIComponent(this.encodeObj(args[i]));
      document.getElementsByTagName('HEAD')[0].appendChild(new Element('script', {'src': this.options.cdurl + params, 'type': 'text/javascript'}));
      return this;
   },

   encodeObj: function(param)
   {
      if (typeof(param) == 'object')
      {
         var obj = "<ajaxarray>";
         for (i in param)
         {
             if (i == 'constructor' || param[i] && ($type(param[i]) == 'function' || $type(param[i]) == 'object')) continue;
             obj += "<k>" + i + "</k><v>" + this.encodeObj(param[i]) + "</v>";
         }
         obj += "</ajaxarray>";
         return obj;
      }
      return param;
   },

   submit: function(func, form, target, url)
   {
      form = $(form);
      var old_target = form.target;
      var old_action = form.action;
      var old_method = form.method;
      var old_enctype = form.encoding;
      url = (url) ? url : this.options.url;
      form.action = url.replace('#', '') + (url.contains('?') ? '&' : '?') + "ajaxfunc=" + encodeURIComponent(func) + "&ajaxsubmit=1&ajaxargs[]=" + encodeURIComponent(this.encodeObj(target.substr(6)));
      form.method = 'post';
      form.target = target;
      form.encoding = 'multipart/form-data';
      form.submit();
      form.target = old_target;
      form.action = old_action;
      form.method = old_method;
      form.encoding = old_enctype;
   },

   submitProgress: function(func, form, target, callback, url)
   {
      this.isShowLoader = false;
      this.submit(func, form, target, url);
      setTimeout(function(){this.progress(target.substr(6), callback);}.bind(this), 500);
   },

   progress: function(id, callback)
   {
      this.doit(callback, $('ajax_progress_key_' + id).value, id);
   },

   showLoader: function()
   {
      if (this.options.isShowLoader)
      {
         if (document.body) document.body.style.cursor = 'wait';
         this.fireEvent('loaderShow');
      }
   },

   hideLoader: function()
   {
      if (this.options.isShowLoader)
      {
         if (document.body) document.body.style.cursor = 'default';
         this.fireEvent('loaderHide');
      }
   },

   startHistory: function()
   {
      this.currentHash = window.location.hash;
      if (window.ie)
      {
         var el = new Element('iframe', {'styles': {'display': 'none'}, 'id': 'ajax_historyFrame'});
         el.inject(document.body, 'top');
         var iframe = $('ajax_historyFrame').contentWindow.document;
         iframe.open();
         iframe.close();
         iframe.location.hash = this.currentHash;
         if (!this.currentHash) this.currentHash = '#';
      }
      this.historyInterval = setInterval(this.history, 100);
   },

   addHistory: function(hash)
   {
      if (window.ie)
      {
         var iframe = $('ajax_historyFrame').contentWindow.document;
         iframe.open();
         iframe.close();
         iframe.location.hash = hash;
      }
      window.location.hash = hash;
   },

   stopHistory: function()
   {
      clearInterval(this.historyInterval);
      this.currentHash = null;
      this.historyInterval = null;
   },

   history: function()
   {
      var hash;
      if (window.ie) hash = $('ajax_historyFrame').contentWindow.document.location.hash;
      else hash = window.location.hash;
      if (this.currentHash != hash)
      {
         this.currentHash = hash;
         if (window.ie) window.location.hash = hash;
         this.fireEvent('history', this.currentHash.substr(1));
      }
   }

});
