/**
 * Subsys_JsHttpRequest_Js: JavaScript DHTML data loader.
 * (C) 2005 Dmitry Koterov, http://forum.dklab.ru/users/DmitryKoterov/
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 * See http://www.gnu.org/copyleft/lesser.html
 *
 * Do not remove this comment if you want to use script!
 * Не удаляйте данный комментарий, если вы хотите использовать скрипт!
 *
 * This library tries to use XMLHttpRequest (if available), and on
 * failure - use dynamically created <script> elements. Backend code
 * is the same for both cases.
 *
 * @author Dmitry Koterov
 * @version 3.34
 */

function Subsys_JsHttpRequest_Js()
{
  this._construct()
}

(
  function()
  { // to create local-scope variables
    var COUNT       = 0;
    var PENDING     = {};
    var CACHE       = {};

    // Called by server script on data load.
    Subsys_JsHttpRequest_Js.dataReady = function(id, text, js)
    {
      var undef;
      var th = PENDING[id];
      delete PENDING[id];
      if (th)
      {
        delete th._xmlReq;
        if (th.caching)
          CACHE[th.hash] = [text, js];
        th._dataReady(text, js);
      }
      else if (typeof(th) != typeof(undef))
      {
        alert("ScriptLoader: unknown pending id: "+id);
      }
    }

    Subsys_JsHttpRequest_Js.prototype =
    {
      // Standard properties.
      onreadystatechange: null,
      readyState:         0,
      responseText:       null,
      responseXML:        null,
      status:             200,
      statusText:         "OK",

      // Additional properties.
      session_name:       "PHPSESSID",  // set to SID cookie or GET parameter name
      responseJS:         null,         // JavaScript response array/hash
      caching:            false,        // need to use caching?
      fallbackToScript:   false,

      // Internals.
      _span:              null,
      _id:                null,
      _xmlReq:            null,
      _openArg:           null,
      _reqHeaders:        null,

      dummy: function() {}, // empty function

      abort: function()
      {
        if (this._xmlReq)
          return this._xmlReq.abort();
        if (this._span)
        {
          this.readyState = 0;
          if (this.onreadystatechange)
            this.onreadystatechange();
          this._cleanupScript();
        }
      },

      open: function(method, url, asyncFlag, username, password)
      {
        this._openArg =
        {
          'method':    method,
          'url':       url,
          'asyncFlag': asyncFlag,
          'username':  username != null? username : '',
          'password':  password != null? password : ''
        };
        this._id = null;
        this._xmlReq = null;
        this._reqHeaders = [];
        return true;
      },

      send: function(content)
      {
        var id = (new Date().getTime()) + "" + COUNT++;

        // Build QUERY_STRING from query hash.
        var query = this._hash2query(content);

        // Append SID to original URL now.
        var url = this._openArg.url;
        var sid = this._getSid();
        if (sid)
          url += (url.indexOf('?')>=0? '&' : '?') + this.session_name + "=" + this.escape(sid);

        // Solve hash BEFORE appending ID.
        var hash = this.hash = url + '?' + query;
        if (this.caching && CACHE[hash])
        {
          var c = CACHE[hash];
          this._dataReady(c[0], c[1]);
          return false;
        }

        // Try to use XMLHttpRequest.
        this._xmlReq = this._obtainXmlReq(id, url);

        // Pass data in URL (GET, HEAD etc.) or in request body (POST)?
        var hasSetHeader = this._xmlReq && (window.ActiveXObject || this._xmlReq.setRequestHeader);
        var href, body;
        var method = (""+this._openArg.method).toUpperCase();
        if (this._xmlReq && hasSetHeader && method == "POST")
        {
          // Use POST method. Pass query in request body.
          // Opera 8.01 does not support setRequestHeader, so no POST method.
          this._openArg.method = "POST";
          href = url;
          body = query;
        }
        else
        {
          if (method != 'GET' && !this.fallbackToScript && query.length > 2000)
          {
            throw 'Cannot use XMLHttpRequest nor Microsoft.XMLHTTP for long POST query: object not implemented or disabled in browser.';
          }
          this._openArg.method = "GET";
          href = url + (url.indexOf('?')>=0? '&' : '?') + query;
          body = null;
        }

        // Append ID: a=aaa&b=bbb&<id>
        href = href + (href.indexOf('?')>=0? '&' : '?') + id;

        // Save loading script.
        PENDING[id] = this;

        if (this._xmlReq)
        {
          // Open request now & send it.
          // In XMLHttpRequest mode request URL MUST be ended with "<id>-xml".
          var a = this._openArg;
          this._xmlReq.open(a.method, href+"-xml", a.asyncFlag, a.username, a.password);
          if (hasSetHeader)
          {
            // Pass pending headers.
            for (var i=0; i<this._reqHeaders.length; i++)
                this._xmlReq.setRequestHeader(this._reqHeaders[i][0], this._reqHeaders[i][1]);
            // Set non-default Content-type. We cannot use
            // "application/x-www-form-urlencoded" here, because
            // in PHP variable HTTP_RAW_POST_DATA is accessible only when
            // enctype is not default (e.g., "application/octet-stream"
            // is a good start). We parse POST data manually in backend
            // library code.
            this._xmlReq.setRequestHeader('Content-Type', 'application/octet-stream');
          }
          // Send the request.
          return this._xmlReq.send(body);
        }
        else
        {
          // Create <script> element and run it.
          this._obtainScript(id, href);
          return true;
      }
    },

    getAllResponseHeaders: function()
    {
      if (this._xmlReq)
        return this._xmlReq.getAllResponseHeaders();
      return '';
    },

    getResponseHeader: function(label)
    {
      if (this._xmlReq)
        return this._xmlReq.getResponseHeader(label);
      return '';
    },

    setRequestHeader: function(label, value)
    {
      // Collect headers.
      this._reqHeaders[this._reqHeaders.length] = [label, value];
    },


    //
    // Internal functions.
    //

    // Constructor.
    _construct: function() {},

    // Do all work when data is ready.
    _dataReady: function(text, js)
    {
      with (this)
      {
        if (text !== null || js !== null)
        {
            readyState = 4;
            responseText = responseXML = text;
            responseJS = js;
        }
        else
        {
            readyState = 0;
            responseText = responseXML = responseJS = null;
        }
        if (onreadystatechange)
        onreadystatechange();
        _cleanupScript();
      }
    },

    // Create new XMLHttpRequest object.
    _obtainXmlReq: function(id, url)
    {
      // If url.domain specified and differ from current, cannot use XMLHttpRequest!
      // XMLHttpRequest (and MS ActiveX'es) cannot work with different domains.
      var p = url.match(new RegExp('^[a-z]+://(.*)', 'i'));
      if (p)
      {
        var curHost = document.location.host.toLowerCase();
        if (p[1].substring(0, curHost.length).toLowerCase() == curHost)
        {
            url = p[1].substring(curHost.length, p[1].length);
        }
        else
        {
            return null;
        }
      }

      // Try to use built-in loaders.
      var req = null;
      if (window.XMLHttpRequest)
      {
          try { req = new XMLHttpRequest() } catch(e) {}
      }
      else if (window.ActiveXObject)
      {
        try
        {
          req = new ActiveXObject("Microsoft.XMLHTTP")
        }
        catch(e)
        {}
        if (!req)
          try
          {
            req = new ActiveXObject("Msxml2.XMLHTTP")
          }
          catch (e)
          {}
      }
      if (req)
      {
        var th = this;
        req.onreadystatechange = function()
        {
          var s = req.readyState;
          if (s == 4)
          {
            // Avoid memory leak by removing closure.
            req.onreadystatechange = th.dummy;
            // Remove possible junk from response.
            var responseText = req.responseText;
            try
            {
              // Call associated dataReady().
              eval(responseText);
            }
            catch (e)
            {
              Subsys_JsHttpRequest_Js.dataReady(id, "JavaScript code generated by backend is invalid!\n"+responseText, null);
            }
          }
          else
          {
            th.readyState = s;
            if (th.onreadystatechange) th.onreadystatechange()
          }
        };
        this._id = id;
      }
      return req;
    },

    // Create new script element and start loading.
    _obtainScript: function(id, href) { with (document)
    {
      var span = null;
      // Oh shit! Damned stupid fucked Opera 7.23 does not allow to create SCRIPT
      // element over createElement (in HEAD or BODY section or in nested SPAN -
      // no matter): it is created deadly, and does not respons on href assignment.
      // So - always create SPAN.
      var span = createElement("SPAN");
      span.style.display = 'none';
      body.appendChild(span);
      span.innerHTML = 'Text for stupid IE.<s'+'cript></' + 'script>';
      setTimeout(function()
      {
        var s = span.getElementsByTagName("script")[0];
        s.language = "JavaScript";
        if (s.setAttribute)
          s.setAttribute('src', href); else s.src = href;
      }, 10);
      this._id = id;
      this._span = span;
    }
  },

  // Remove last used script element (clean memory).
  _cleanupScript: function()
  {
    var span = this._span;
    if (span)
    {
      this._span = null;
      setTimeout(function()
      {
        // without setTimeout - crash in IE 5.0!
        span.parentNode.removeChild(span);
      }, 50);
    }
    return false;
  },

  // Convert hash to QUERY_STRING.
  _hash2query: function(content, prefix)
  {
    if (prefix == null) prefix = "";
      var query = [];
    if (content instanceof Object)
    {
      for (var k in content)
      {
        var v = content[k];
        if (v == null || ((v.constructor||{}).prototype||{})[k])
          continue;
        var curPrefix = prefix? prefix+'['+this.escape(k)+']' : this.escape(k);
        if (v instanceof Object)
          query[query.length] = this._hash2query(v, curPrefix);
        else
          query[query.length] = curPrefix + "=" + this.escape(v);
      }
    }
    else
    {
      query = [content];
    }
    return query.join('&');
  },

  // Return value of SID based on QUERY_STRING or cookie
  // (PHP compatible sessions).
  _getSid: function()
  {
    var m = document.location.search.match(new RegExp('[&?]'+this.session_name+'=([^&?]*)'));
    var sid = null;
    if (m)
    {
      sid = m[1];
    }
    else
    {
      var m = document.cookie.match(new RegExp('(;|^)\\s*'+this.session_name+'=([^;]*)'));
      if (m)
        sid = m[2];
    }
    return sid;
  },

  // Stupid JS escape() does not quote '+'.
  escape: function(s)
  {
    return escape(s).replace(new RegExp('\\+','g'), '%2B');
  }
}
}
)();
