/* 
//////////////////////////////////////////////////////////////////

   AJAX Client for SPARQL Protocol for RDF
   
   Author: Leigh Dodds (leigh@ldodds.com)
   
   Version:
   $Id$

   Example usage:

   ----------------------------------------
   
   client = new SparqlClient();
   
   //set base url of SPARQL query service
   client.base='http://xmlarmyknife.org/api/rdf/sparql/query';
   //configure the default graph(s) to query
   graphs = new Array();
   graphs[0] = 'http://www.ldodds.com/ldodds.rdf';
   client.graphs = graphs;

   //send a query
   client.send('SELECT * WHERE {?x ?y ?z.}');

   ----------------------------------------

   To do something useful with the returned results you'll 
   need to register two callback functions. One to handle 
   errors, the other to handle the results.
   
   E.g:

   ----------------------------------------
   
   function processResults() {
   alert(this.results.results.length + ' results');
   }
   
   function processError(code) {
   alert('Oh No! We got this error code: ' + code);
   }
   
   client.handleResults = processResults;
   client.handleError = processError;

   ----------------------------------------

   The above code shows how to register the callback 
   functions. The functions operate as methods on the 
   SparqlClient object so have access to the this pointer 
   for manipulating the Results object, query, etc.

//////////////////////////////////////////////////////////////////
*/

//////////////////////////////////////////////////////////////////

SPARQL_NS = 'http://www.w3.org/2005/sparql-results#';


//////////////////////////////////////////////////////////////////
   Resource
//////////////////////////////////////////////////////////////////
   
function Resource()
{
  this.value = '';
  this.anon = false;
}

Resource.prototype.toString = function()
{
  return this.value;
}

Resource.prototype.isAnon = function()
{
  return this.anon;
}

//////////////////////////////////////////////////////////////////
   Literal 
//////////////////////////////////////////////////////////////////
   
function Literal()
{
  this.value = '';
  this.language = '';
  this.datatype = ''
}

Literal.prototype.toString = function()
{
   if (this.language != '')
   {
      return this.value + '@' + this.language;   
   }
   if (this.datatype != '')
   {
      return this.value + '^^' + this.datatype;
   }
   return this.value;
}

//////////////////////////////////////////////////////////////////
   Results
//////////////////////////////////////////////////////////////////

function Results()
{
   this.ordered = false;
   this.distinct = false;
   this.results = new Array();
}

Results.prototype.size = function() {
  return this.results.length;
}

//////////////////////////////////////////////////////////////////
   SparqlClient
//////////////////////////////////////////////////////////////////

function SparqlClient()
{
   this.results = new Results();
   this.base = 'http://xmlarmyknife.org/api/rdf/sparql/query';
   this.graphs = new Array();
   this.query = '';

   this.responseHandler = new ResponseHandler(this);
}

SparqlClient.prototype.createRequest = function()
{
   var ua = navigator.userAgent.toLowerCase();
   if (!window.ActiveXObject)
   { 
     this.request = new XMLHttpRequest();
   }
   else if (ua.indexOf('msie 5') == -1)
   {
     this.request = new ActiveXObject("Msxml2.XMLHTTP");
   }
   else
   {
     this.request = new ActiveXObject("Microsoft.XMLHTTP");
   }
   
   this.request.onreadystatechange = this.responseHandler.handle;
}

SparqlClient.prototype.send = function(query)
{
   this.createRequest();
   this.query=query;
   
   //mozilla specific to force responseXML to be populated
   //its a horrible hack!
   if (!window.ActiveXObject)
   { 
   this.request.overrideMimeType('text/xml');
   }
   
   try
   {
      this.request.open('GET', this.base + this.buildParams(), true);   
      this.request.send(null);
   } catch (e)
   {
      msg = '';
      if ( e.description != null)
      {
         msg = e.description;
      }
      else
      {
         msg = e;
      }
      alert('Unable to run query. Error was:\n' + msg );
   }
}

SparqlClient.prototype.buildParams = function()
{
   //iterate over array and add params
   //add query
   query = '?';
   
   for (i=0;i<this.graphs.length;i++)
   {
     
     query = query + 'default-graph-uri=' + graphs[i];
     if (i+1<this.graphs.length)
     {
        query=query+'&';
     }
   }
   
   query = query + '&query=' + escape(this.query);

   return query;
}

SparqlClient.prototype.handleResults = function()
{
   alert('Returned ' + this.results.results.length 
         + ' results from query: ' + this.query);
}

SparqlClient.prototype.handleError = function(code)
{
   alert(code + 
      ' error response returned from query: ' + this.query);
}

//////////////////////////////////////////////////////////////////
   ResponseHandler
//////////////////////////////////////////////////////////////////

function ResponseHandler(client)
{
  this.client = client;
  me = this;
}

ResponseHandler.prototype.handle = function()
{
  if (me.client.request.readyState != 4) 
  {
     // still not ready
  } else 
  {
    //do our stuff.    
    if (me.client.request.status==500)
    {
       me.client.handleError(me.client.request.status);
    }
    else if (me.client.request.status == 400)
    {
       me.client.handleError(me.client.request.status);
    }
    else if (me.client.request.status != 200)
    {
      me.client.handleError(me.client.request.status);    
    }
    else
    {            
      xml = me.client.request.responseXML;
      
      //This is an IE specific hack -- for some reason responseXML
      //wasn't being set properly. Possibly similar to Mozilla issue 
      //with mime types. Anyway, tell it to parse the text..
      if (window.ActiveXObject)
      { 
           me.client.request.responseXML.loadXML(me.client.request.responseText);     
      }
      
      //IE doesn't like this method      
      //resultsNodeList = xml.getElementsByTagNameNS(SPARQL_NS, 'results');
            
      resultsNodeList = xml.getElementsByTagName('results');
      
      resultsElement = resultsNodeList[0];
            
      me.client.results = new Results();
      me.client.results.ordered = resultsElement.getAttribute('ordered');
      me.client.results.distinct = resultsElement.getAttribute('distinct');
      
      //loop through results and add to the array.
      //IE doesn't like this method            
      //theResults = resultsElement.getElementsByTagNameNS(SPARQL_NS, 'result');
      theResults = resultsElement.getElementsByTagName('result');
      
      for (i=0; i<theResults.length;i++)
      {        
        theResult = theResults[i];        
        
        //bindings = theResult.getElementsByTagNameNS(SPARQL_NS, 'binding')
        bindings = theResult.getElementsByTagName('binding')
      	
        row = new Array();
        
        //loop through bindings and create  
        for (j=0; j<bindings.length;j++)
        {                  
           binding = bindings[j];
           
           name = binding.getAttribute('name');

           child = binding.getElementsByTagName("*")[0];

           if (child.nodeName == 'unbound')
           {
             row[name] = null;
           }
           if (child.nodeName == 'uri')
           {
             row[name] = new Resource();
             row[name].value = child.firstChild.nodeValue;
           }
           if (child.nodeName == 'bnode')
           {                                     
             row[name] = new Resource();
             row[name].value = child.firstChild.nodeValue;
             row[name].anon = true;
           }
           if (child.nodeName == 'literal')
           {
             row[name] = new Literal();
             row[name].value = child.firstChild.nodeValue;
             if ( child.getAttribute('xml:lang') != null )
             {
             	row[name].language = child.getAttribute('xml:lang');
             }
             if ( child.getAttribute('datatype') != null )
             {
             	row[name].datatype = child.getAttribute('datatype');
             }
           }
                   
        }
        
        me.client.results.results[i] = row;        

      }
      
      me.client.handleResults();
    }
  }

}
