/** Net - XMLHTTP Interface - bfults@gmail.com - 2006-08-29                 **
 ** Code licensed under Creative Commons Attribution-ShareAlike License     **
 ** http://creativecommons.org/licenses/by-sa/2.5/                          **/

/** Net asynchronous request library **/
Net = function()
{
  this.Request = Net._createRequestObject();
}

/** Net.get({
   url: [string] -- URL to make the request to,
   vars: [string|object] (optional) -- object or string of querystring vars,
   onsuccess: [function] -- function reference to call on success,
   onerror: [function] (optional) -- function reference to call on error
 }) -- make an HTTP GET request
 ** Returns true on successful request dispatch, false on error.
 **/
Net.get = function(oArgs)
{
  if (oArgs.url && oArgs.onsuccess)
  {
    if (typeof oArgs.vars == "object")
    {
      oArgs.vars = '?'+ Net._serializeObject(oArgs.vars);
    }
    else if (typeof oArgs.vars == "string"
             && oArgs.vars.length > 0
             && !/\?/.test(oArgs.vars))
    {
      oArgs.vars = '?' + oArgs.vars;
    }
    if (!oArgs.vars) oArgs.vars = '';
    oArgs.onerror = oArgs.onerror || Net._fnErrorDefault;
    try {
      var N = new Net();
      N.Request.open("GET", oArgs.url + oArgs.vars, true);
      N._setCallback(oArgs.onsuccess, oArgs.onerror);
      N.Request.send('');
    }
    catch (e)
    {
      oArgs.onerror("initialization");
      return false;
    }
  }
  else
  {
    return false;
  }
  return true;
}

/** Net.post({
   url: [string] -- URL to make the request to,
   vars: [string|object] (optional) -- object or string of post vars,
   onsuccess: [function] -- function reference to call on success,
   onerror: [function] (optional) -- function reference to call on error
 }) -- make an HTTP POST request
 ** Returns true on successful request dispatch, false on error.
 **/
Net.post = function(oArgs)
{
  if (oArgs.url && oArgs.onsuccess)
  {
    if (typeof oArgs.vars == "object")
    {
      oArgs.vars = Net._serializeObject(oArgs.vars);
    }
    if (!oArgs.vars) oArgs.vars = '';
    oArgs.onerror = oArgs.onerror || Net._fnErrorDefault;
    try {
      var N = new Net();
      N.Request.open("POST", oArgs.url, true);
      N._setCallback(oArgs.onsuccess, oArgs.onerror);
      N.Request.setRequestHeader("Method", "POST "+oArgs.url+" HTTP/1.1");
      N.Request.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
      N.Request.send(oArgs.vars);
    }
    catch (e)
    {
      oArgs.onerror("initialization");
      return false;
    }
  }
  else
  {
    return false;
  }
  return true;
}

// a default error function showing the structure (does nothing)
Net._fnErrorDefault = function(sType, Request)
{
  switch (sType)
  {
    case "timeout":
    // it was a timeout (Request undefined)
    Eventful.console.error("XMLHTTP timeout.");
    break;

    case "initialization":
    // initialization error (Request undefined)
    Eventful.console.error("XMLHTTP initialization failure.")
    break;

    default:
    // other error (HTTP status, etc.)
    try
    {
      Eventful.console.error("XMLHTTP error: ["+ Request.status +"] "+
        Request.statusText);
    }
    catch (e) {
      Eventful.console.error("Net error: "+e);
    }
  }
}

// a helper function to serialize an object
Net._serializeObject = function(oFrom)
{
  var aTemp = [];
  for (var i in oFrom)
  {
    aTemp.push(encodeURIComponent(i) +"="+ encodeURIComponent(oFrom[i]));
  }
  return aTemp.join('&');
}

/** Net._setCallback(fnCallback, fnError)
 ** Attaches (and wraps) a request object with a user-defined callback.
 ** Optional fnError: call upon erroneous HTTP status code or timeout.
 **/
Net.prototype._setCallback = function(fnCallback, fnError)
{
  this.Request.onreadystatechange = (function (oNet)
  {
    return function()
    {
      if (oNet.Request.readyState == 4)
      {
        window.clearTimeout(oNet.timeout);

        try {
          if (oNet.Request.status === undefined
            || oNet.Request.status === 0
            || (oNet.Request.status >= 200 && oNet.Request.status < 300)
            || oNet.Request.status == 304)
          {
            fnCallback(oNet.Request);
          }
          else
          {
            fnError("other", oNet.Request);
          }
        } catch (e) {
          fnError("other", oNet.Request, e);
        }
      }
    }
  })(this);

  this.timeout = window.setTimeout((function(oNet) {
    return function() {
      oNet.Request.onreadystatechange = function() {};
      oNet.Request = null;
      fnError("timeout"); }})(this), 20000);
}

/** Net._createRequestObject()
 ** Creates and returns an XMLHTTP element or null on failure.
 **/
Net._createRequestObject = function()
{
  var xmlhttp;
  if (window.XMLHttpRequest) { xmlhttp = new XMLHttpRequest(); }
  else if (window.ActiveXObject) {
    try { xmlhttp = new ActiveXObject("Msxml2.XMLHTTP"); }
    catch (e) { try { xmlhttp = new ActiveXObject("Microsoft.XMLHTTP"); }
    catch (e) { xmlhttp = null; }}
  }
  return xmlhttp;
}
/** End Net asynchronous request library */

/* no-op console functions for non-Firebug browsers */
var nop = function() { return true; };
Eventful._console = {error:nop, info:nop, debug:nop, warn:nop, log:nop};
Eventful.console = Eventful._console;
if (window.console && window.console.firebug)
{
  Eventful.console = window.console;
}
/* end no-op console functions */

function make_xhr()
{
  alert("Nope. Use Net.get() or Net.post(). See /js/dhtml.js");
}

/*********************************************************************
 * replaceContentsWithText
 *
 * Replaces the innerHTML of an object, referenced by id, with the
 * given text.
 *********************************************************************/
function replaceContentsWithText(id, text)
{
  var elem = document.getElementById(id);
  elem.innerHTML = text;
}


/*********************************************************************
 * replaceContentsWithURL
 *
 * Given an object ID (probably a div) and a URL, this function
 * fetches the URL and replaces the innerHTML of the object with
 * the results.  The URL should return an HTML snippet, not an
 * entire HTML document (with <html><body>, etc.), since it will
 * become part of an existing document.
 *********************************************************************/
function replaceContentsWithURL(id, url)
{
  var elem = document.getElementById(id);
  Net.get({
    url: url,
    onsuccess: function(R) { elem.innerHTML = R.responseText; },
    onerror: function(sType, R) { elem.innerHTML = "Error. Please try again."; }
  });
}

/*******************************************************************
 * replaceContentsWithFormResults
 *
 * Similar to replaceContentsWithURL, rather than a URL to fetch,
 * it takes a form name.  The form is submitted (POST) and the results
 * are stuffed into the target object.  The action parameter of the
 * form  is constructed like this:
 * action="javascript:replaceContentsWithFormResults('target_id','form_name');"
 *******************************************************************/
function replaceContentsWithFormResults(id, formName, url)
{
  var postVars = buildPOST(formName);
  var elem = document.getElementById(id);
  Net.post({url:url, vars:postVars, onsuccess:get_replacer(elem)});
}

/*********************************************************************
 * buildPOST
 *
 * Constructs a query-string-like POST body.
 *********************************************************************/
function buildPOST(formName)
{
  var theForm = document.forms[formName];
  var qs = '';
  for (e=0;e<theForm.elements.length;e++)
  {
    if(theForm.elements[e].name != '')
    {
      var name = theForm.elements[e].name;
      qs+=(qs=='')?'':'&';
      qs+= encodeURIComponent(name)+
        '='+encodeURIComponent(theForm.elements[e].value);
    }
  }
  return qs;
}

/*********************************************************************
 * get_replacer
 *
 * get_replacer returns a function suitable for use as request's
 * onreadystatechange value which, when called, will replace element's
 * content with request's response text.
 *********************************************************************/
function get_replacer(el)
{
  return function(R)
  {
    el.innerHTML = R.responseText;
  }
}

function alerter(R)
{
  alert(R.statusText+' / '+R.responseText);
}

/*******************************************************************
 * blindGET
 *
 * Make an HTTP GET request, but ignore the answer.
 *******************************************************************/
function blindGET(url, debug)
{
  // if we're making a cross-domain request
  if (!/eventful.com(\/|$)/.test(url))
  {
    if (document.createElement)
    {
      var elGet = document.createElement("img");
      document.body.appendChild(elGet);
      elGet.style.position = "absolute";
      elGet.style.visibility = "hidden";
      elGet.src = url;
    }
  }
  // else it's a same-domain request
  else
  {
    var cb = (debug) ? alerter : function() { return true; };
    Net.get({url:url, onsuccess:cb});
  }
}

function callbackGET(url,callback)
{
  Net.get({url: url, onsuccess: callback});
}

/* Tag and link deletion */

function tag_squish(liTag) {
  var link = liTag.getElementsByTagName('a');
  if (!link) { return ''; }
  var href = link[0].getAttribute('href');
  return href.split('/').pop();
}

function delete_tag_onclick(type, sid, liTag) {
  var squished = tag_squish(liTag);
  return function () {
    EVDB.API.call('/' + type + '/tags/remove',
                  {
                    tag:      squished,
                    id:       sid
                  },
                  function (response) {
                    if (response.status == 'ok') {
                      liTag.parentNode.removeChild(liTag);
                    }
                  });
  };
}

function delete_link_onclick(type, sid, liLink) {
  var link_id = liLink.getAttribute('id').substring(5);
  return function () {
    EVDB.API.call('/' + type + '/links/remove',
                  {
                    link_id:  link_id,
                    id:       sid
                  },
                  function (response) {
                    if (response.status == 'ok') {
                      liLink.parentNode.removeChild(liLink);
                    }
                  });
  };
}

function attach_delete_actions(sItem, s_id, sAttr)
{
  // get the container element
  var elContainer = document.getElementById(sAttr/* +"_container" */);
  if (elContainer)
  {
    // get all of the list items that we're interested in
    var lsItems = elContainer.getElementsByTagName("li");
    var i = 0, elLI = null, lsDeleteBox = null, elDeleteBox = null;
    while (elLI = lsItems[i++])
    {
      // grab a span if it exists (we assume it's the delete box)
      lsDeleteBox = elLI.getElementsByTagName("span");
      if (lsDeleteBox.length)
      {
        elDeleteBox = lsDeleteBox[0];

        // differentiate based on attribute; we make different API calls
        switch (sAttr)
        {
          case "tags":
          Events.addHandler(elDeleteBox, "click",
            delete_tag_onclick(sItem, s_id, elLI));
          break;

          case "links":
          Events.addHandler(elDeleteBox, "click",
            delete_link_onclick(sItem, s_id, elLI));
          break;
        }
      }
    }
  }
}
