/************************************************************
 * 
 * Project: AJAR/Tabulator
 * 
 * File: sources.js
 * 
 * Description: contains functions for requesting/fetching/retracting
 *  'sources' -- meaning any document we are trying to get data out of
 * 
 * SVN ID: $Id: sources.js 1273 2006-07-13 18:16:24Z alerer $
 *
 ************************************************************/
/*jsl:option explicit*/

// Bonus prize for a better way to do this. This refreshes the whole
// browser table for any icons which need to change state when URI has loaded

// Suggestion 1: Look for the resource URI linearly through the code,
// and when you find it, take the next img tag and change it

// Suggestion 2: Save a table of all the URI/image pairs and lookup when you
// want to refresh.

function refreshButtons(resourceURI, iconURI, ele, parent) {
//    fyi("    Refresh name "+ ele + " name " + ele.tagName)
    if (typeof ele == 'undefined') {ele = document.getElementById('browser');}
    if (typeof {'td':1,'tr':1,'tbody':1,'table':1,'span':1,'TD':1,'TR':1,'TBODY':1,'TABLE':1,'SPAN':1}[ele.tagName]!='undefined') {
        var nodes = ele.childNodes, len = nodes.length, i;
        for (i=0; i<len; i++) refreshButtons(resourceURI, iconURI, nodes[i], ele);
    } 
    else if (ele.tagName == 'IMG') {
        var about = parent.getAttribute('about').replace(/^</, "").replace(/>$/,"");
        about = about.replace(/#.*/,"");
        var tsrc = ele.src;
        var j = tsrc.indexOf('/icons/');//finds the icons folder in the tabulator
        if (j >=0 ) tsrc=tsrc.slice(j+1); // get just relative bit we use

        if ((about == resourceURI) 
        && ((tsrc == icon_requested)
        ||(tsrc == icon_unrequested)
        ||(tsrc == icon_failed))) {
            fyi("Fixed an icon");
            ele.src = iconURI;
        }
    }
} //refreshButtons


/*var sources = { 
  status : [ ], 
  index : 0,
  callbacks : {},
  depends   : {} };
documentStatus = sources.status;*/

/** request a new source (RDFSymbol) **/
function sources_request_new(subject, requestedBy)
{
    var uri = uri_docpart(subject.uri);
    var state = docState(uri);
    if (requestedBy) {
        var dep = requestedBy.toString(); //hashString?
        tdebug("adding " + uri + " to dependencies for " + dep);
        if (!sources.depends[dep]) sources.depends[dep] = []; //Stick the name of the requester into the sources.depends hash
        sources.depends[dep].push(uri);
        //tdebug("depends[dep] now: " + sources.depends[dep].toString());
        //tdebug("sources.depends: " + sources.depends.toString() + 
        //        ", length=" + sources.depends.length + 
        //        ", typeof=" + typeof sources.depends);
        //for (var d in sources.depends) tdebug("d=" + d + ", sources.depends[d]=" + sources.depends[d]);
    } //dependencies
    if (state != 'unrequested' && state != 'failed') return;
//  if (sources.status[uri].state == 'fetched' || sources.status[uri].state == 'requested') return;
    sources.status[uri] = { state: '', number: sources.index}; // Lock URI
    tinfo ("sources_request_new: uri, i: " + uri + ", " + sources.index);
    sources.index++; //There's one more source
    sources_add_html(uri);  //
    sources_fetch(uri);
} //request new subject

/** fetch a uri(string) / src(rdfsymbol) **/
function sources_fetch(uri, callback)
{
//    tdebug("entering sources_fetch " + uri);

    if (!sources_fetchable(uri)) {
        terror("uri is unfetchable: " + uri + ", state: " + docState(uri));
        return;
    } //check uri
    if (!callback) callback = sources_loaded;
    sources.status[uri].state = 'requested';
    refreshButtons(uri, icon_requested);
    var request = sources_xmlhttprequest(uri, function() { callback(request, uri); }); //request
        
    // try to get privileges
    //if (uri.slice(7, document.domain.length) != document.domain) {
      if (1==1){
        try {
            netscape.security.PrivilegeManager.enablePrivilege( "UniversalBrowserRead");            
	    tinfo("Got UniversalBrowserRead");

        } catch(e) {
            alert("(Please see the Tabulator Help!) Failed to get privilege UniversalBrowserRead to open "
        +uri+ ", " + e);
        }
    }
    try { netscape.security.PrivilegeManager.enablePrivilege( "UniversalXPConnect"); 
    } catch (e) { alert("couldn't get xp connect"); }

    //tdebug ("initialized request: " + request + request.onreadystatechange);
    var uri2 = mapURI(uri) //@@
    request.open('GET', uri2, true);
    try {
	request.setRequestHeader('Accept','application/rdf+xml')
    } catch (er) {
	twarn("Can't set Accept: application/rdf+xml: "+er)
    }
    try {
      request.overrideMimeType('text/xml');  // Doesn't work for opera //sucks to be opera
    } catch (er) { 
      twarn("can't override mime type. " /*+ er */);
    } //set mimetype to text/xml cos otherwise firefox complains
    request.send(null);
    setTimeout(function() { 
        if (request.readyState != 4) {
            request.abort(); 
            sources.status[uri].state = 'failed'; 
            sources_update_html(uri, 'timed out'); 
            refreshButtons(uri, icon_failed); } }, 30000); //30-second timeout
} //sources_fetch

/** register a callback **/
function sources_register_cb( trigger, callback) {
    trigger = trigger.toString(); //hashString()? 
    tdebug("registering a callback on trigger=" + trigger); //+ ", callback: " + callback);
    if (!sources.callbacks[trigger]) sources.callbacks[trigger] = [];
    sources.callbacks[trigger].push(callback);
    //tdebug("callbacks[trigger] now: " + sources.callbacks[trigger].join());
} //callback


/** similar to documentLoaded **/
function sources_loaded(request, uri) { // Significance of return value?? -tim

    if (request.readyState != 4) {
//	tdebug("entering sources_loaded, readyState=" + request.readyState + ", uri=" + uri);
	return false; //state 4 = complete
    }
    tdebug( "sources_loaded ready, request=" + request + ", uri=" + uri);
    var newIcon, statuscode;
    try  { 
        statuscode = request.status;
    } catch (e) {
        terror ("couldn't get request.status: e=" + e);
        statuscode = -1; //failure
    } //try-catch
    if ((statuscode == 301) ||(statuscode == 302) 
    ||(statuscode == 303)) {
        try {
            var loc = request.getResponseHeader('Location');
        } catch (e) {
            terror("no Location header for request=" + request + ", uri=" + uri);
        } //try-catch
        tmsg("Redirect "+statuscode+" for " + uri +" to "+loc);
        if (loc.indexOf('#') >= 0) {
            twarn("Redirect URI contains #, Fragment ignored:"+loc);
            loc = uri_docpart(loc);
        }
        sources.status[uri].state = 'fetched'; // or wait for new doc?
        newIcon = icon_fetched;
        requestFetch(kb.sym(loc));
    } else if (statuscode == 404) {
        terror("No data provided by URI owner: HTTP "+statuscode+" code for " + uri);
        sources.status[uri].state = 'failed';
        newIcon = icon_failed;
    } else if (statuscode != 200) {
        terror("Sorry, can't handle "+statuscode+" code for " + uri);
        sources.status[uri].state = 'failed';
        newIcon = icon_failed;
        //sources_update_html(uri, statuscode);
        //refreshButtons(uri, icon_failed);
        //return false
    } else { // 200
        tdebug("sources_loaded: HTTP 200, request=" + request);
        sources_update_html(uri, "parsing");
        setTimeout(function() { //two possibilities -- syntax error, or a reaaally long file. assume the first.
            if (docState(uri) != 'fetched' && docState(uri) != 'failed') { 
                sources.status[uri].state = 'failed'; 
                sources_update_html(uri, 'parse timeout'); 
                refreshButtons(uri, icon_failed); } }, 30000); //30-second timeout
        var success = benchmark(sources_parse, request, uri);
        if (success) {
            sources.status[uri].state = 'fetched';
            addMentions(uri);
            newIcon = icon_fetched;
        } else {
            sources.status[uri].state = 'failed';
            terror("Fetched but could not parse "+uri)
                    newIcon = icon_failed;
        } //set icon state
    }
    sources_update_html(uri, statuscode);
    refreshButtons(uri, newIcon);
    tinfo("UI updated: "+ uri);
    
    findLabelSubProperties()  // because store changed
    
    //check for callbacks
    tinfo("checking for callbacks: ");
    //for (var c in sources.callbacks) tdebug("c=" + c + ", sources.callbacks[c]=" + sources.callbacks[c]);
    //for (var d in sources.depends) tdebug("d=" + d + ", sources.depends[d]=" + sources.depends[d]);
    for (var t in sources.callbacks) { //for each trigger
        tdebug("trigger=" + t + ", depends on=" + sources.depends[t]);
        if (sources.depends[t] && filter(sources_pending, sources.depends[t]).length != 0)
            continue; //still dependencies
        tsuccess("all dependencies loaded for " + t + ", completed files=" + sources.depends[t]);
        if (!sources.callbacks[t])
            tinfo("no callback for trigger " + t);
        else {
            for (var c in sources.callbacks[t]) {
                tdebug("executing callback #" + c + " for: " + t); // + ", " + sources.callbacks[t][c]);
                sources.callbacks[t][c](); //call back
            } //for
            delete sources.callbacks[t]; //unset this trigger
        } //has callbacks
    } //for each trigger
    tinfo("\nFetch over and all actions done: "+ uri);
} //sources_loaded
sources_parse.toString = function () { return "sources_parse"; };

function addMentions(uri)
{
	var subject = new RDFSymbol(uri);
	var statements = kb.statementsMatching(
           undefined, rdf('type'), undefined, subject)
    n = statements.length
    for (var x=0; x<n;x++)
    {
    	kb.add(subject,
    		new RDFSymbol('http://dig.csail.mit.edu/2005/ajar/ajaw/ont#mentionsClass'),statements[x].object, subject)
    	//kb.add(subject,rdf('type'),statements[x].object, subject)
    	sources_request_new(new RDFSymbol('http://dig.csail.mit.edu/2005/ajar/ajaw/ont'))
    }
}

/** returns true if the source has not yet been requested / fetched or has dependencies**/
function sources_pending(uri) {
    //first check sources
    if (docState(uri) == 'fetched' || docState(uri) == 'failed') { //check dependencies
        var n3 = kb.sym(uri).toNT();
        if (sources.depends[n3]) //has dependencies
            return (filter(sources_pending, sources.depends[n3]).length != 0); //still some pending
        else //no dependencies
            return false;
    } 
    return true;
} //fetched or failed, let's not have anything else

/** construct a cross-browser request, override mime type, set onreadystatechange **/
function sources_xmlhttprequest(uri, callback) {
    tdebug("entering sources_xmlhttprequest with " + uri); // + ", " + callback);
    var request = XMLHTTPFactory()
    
    if (!request) {
        terror("Error initializing XMLHttpRequest!");
        return false;
    } //darn.

    //set some typical things
    request.onreadystatechange = callback;
    return request;
} //sources_xmlhttprequest

/** hack around firefox permission shtuff **/   
function sources_xml(request) {
    return (new DOMParser()).parseFromString(request.responseText, 'text/xml');
} //sources_xml

/** parse a source based on content_type, returns true if successful **/
function sources_parse(request, uri) {
    tdebug("entering sources_parse w/ request " + request);
    try {
        var content_type = request.getResponseHeader('Content-Type'); 
    } catch (e) {
        terror("no Content-Type header for uri=" + uri);
        tdebug("full headers: " + request.getAllResponseHeaders());
    } //try-catch content_type  
    if (content_type.indexOf(';') > 0)
	content_type = content_type.slice(0, content_type.indexOf(';')); //no quality info
    tdebug("content_type : " + content_type +" for "+uri);
    var rdfxml = false;
    var success = false;
    switch (content_type) {
        case 'text/html':  /* jsl:fallthru */
        case 'application/xhtml+xml':
	    var meta = sources_metadata(sources_xml(request)); //hack
	    if (meta) {
		tdebug("meta information for uri=" +uri + ": " + meta.join(", "));
		for (var m in meta) {
		    sources_request_new(kb.sym(URIjoin(meta[m], uri)));
		}
	    } //metadata
	    var xmldom = sources_xml(request);
	    sources_RDF_HTMLTransform(xmldom, uri);
	    success = true;
	    break;

	case 'text/xml':  /* jsl:fallthru */
        case 'application/xml':
	        //first check if vanilla rdf
	    var xmldom = sources_xml(request);
            //tdebug("xmldom: " + xmldom + xmldom.length);
            //tdebug("xmldom.documentElement: " + xmldom.documentElement + xmldom.documentElement.length + ", " + xmldom.documentElement.tagName);
            
            if (xmldom.documentElement.tagName == 'rdf:RDF') {
                //parse as RDF
		tdebug("parse: doc ele is <rdf:RDF> so will parse as RDF");
                rdfxml = xmldom.documentElement;
            } else {
                //xslt
		tdebug("parse: doc ele is NOT <rdf:RDF> . Unknowmn XML. " +
				xmldom.documentElement.tagName);
		success =  true // because we have no expectations of data
	    }
	    break;
        case 'text/plain':
        if (request.responseText.slice(0,500).indexOf('xmlns') < 0) break; // hack
                twarn("Interpreting text/plain as RDF/xml for uri=" + uri+
		"starts: '"+ request.responseText.slice(0,30)+"'");
		/* jsl:fallthru */
        case 'application/rdf': /* jsl:fallthru */
        case 'text/rdf':	/* jsl:fallthru */
        case 'application/rdf+xml':
                rdfxml = sources_xml(request);
        rdfxml = rdfxml.documentElement;
        break;
        case 'application/n3': /* jsl:fallthru */
        case 'text/n3': /* jsl:fallthru */
        case 'text/rdf+n3': 
                rdfxml = false; //N3Parser.parse(request.responseText);
		twarn("Sorry, can't parse N3 yet")
        break;
        default:
                rdfxml = false;
    } //switch
    if (rdfxml) {
        tdebug("got response xml");
        success = (new RDFParser(kb)).parse(rdfxml, uri, kb.sym(uri), true);
    } //parse

    return success;
} //sources_parse

/** checks an DOM tree for meta links pointing to rdf and returns their uris as an array
    * or returns false if no links found **/
function sources_metadata(dom) {
    var cands = dom.getElementsByTagName('link');
    tdebug("cands: " + cands + cands.length);
    //var arr = new Array();
    //for (var i = 0; i < cands.length; i++) arr[i] = cands[i];
    var arr = HTMLCollection_to_Array(cands);
    //closure issues
    arr = filter(function(x) { return ( x.getAttribute('rel') == 'meta' || 
                                        x.getAttribute('rel') == 'seeAlso' || 
                                        x.getAttribute('rel') == 'alternate')},
		arr);
    tdebug("Metadata resources: " + arr.join(", "));
    if (arr.length == 0)
        return false;
    else
        return map(function(x) { return x.getAttribute('href'); }, arr);
} //sources_metadata

/** checks a DOM tree for transformation links and applies them, returning the new DOM tree
    * or false if unable to do so **/
function sources_RDF_HTMLTransform(dom, uri) {
    var head = dom.getElementsByTagName('head')[0]; //one would hope there's only one.
    if (!head) { //do some pure xml stuff here
        return false;
    } //only xhtml so far
    var profile = " " + head.getAttribute('profile') + " ";
    if (profile.indexOf(' http://') >= 0) {
        tinfo("GRDDL: Using generic 2003/11/rdf-in-xhtml-processor.");
        sources_request_new(kb.sym('http://www.w3.org/2005/08/online_xslt/xslt?' + 
        'xslfile=http://www.w3.org/2003/11/rdf-in-xhtml-processor&xmlfile=' + uri));
        return true;
    } else {
        tinfo("No GRDDL: No profile attribute on the head element.");
        return false;
    } //spec
} //sources_RDF_HTMLTransform

/** refresh an already loaded src.. does not completely retract **/
function sources_refresh(uri)
{
    if (!sources_fetchable(uri)) {
        twarn("sources_refresh: uri not fetchable: " + uri);
        return;
    } //check
    
    var src = kb.sym(uri);
    if (sources.status[uri]) {
        kb.removeMany(undefined, undefined, undefined, src); //no limit
        sources.status[uri].state = 'retracted';
        sources_fetch(uri);
    } else {
        twarn("called refresh, but source not yet loaded... loading as new");
        sources_request_new(src);
    }
} //refresh

/** completely(!) retrach a source from the kb **/
function sources_retract(uri)
{ 
    var src = kb.sym(uri_docpart(uri));
    sources_remove_html(sources.status[uri].number);
    kb.removeMany(undefined, undefined, undefined, src); //no limit
    sources.status[uri] = undefined;
    refreshButtons(uri, icon_unrequested); //as if it had never been
} //retract

/** fetch an RDFSymbol (OVERWRITE old requestFetch) use request_new **/
function requestFetch(subject) {
    sources_request_new(subject);
} //requestFetch

/** whether or not a source is fetchable **/
function sources_fetchable(uri)
{
    var state = docState(uri);
    tdebug("sources_fetchable: state=" + state + ", uri="+uri);
    //if unpermitted or already requested, don't fetch again!
    return (state != 'unpermitted' && state != 'requested');
} //sources_valid_state

/** add a row to the sources table **/
function sources_add_html(uri)
{
    var i = sources.status[uri].number; //index
    var x = document.getElementById('sources');
    if (!x) terror("no sources table!");
    if (x) {
        var addendum = document.createElement("tr");
        addendum.setAttribute('class', 'requested');
        addendum.setAttribute('id', 'source'+i);
        
        var td = document.createElement('td');
        addendum.appendChild(td);
        var tn = document.createTextNode(uri);
        td.appendChild(tn);
        td.setAttribute('id','Source:'+uri);
        td = document.createElement('td');
        addendum.appendChild(td);
        tn = document.createTextNode('requested');
        td.appendChild(tn);
        var actions = document.createElement('td');
        
        var retr = AJARImage(icon_retract, 'retract');
        var refr = AJARImage(icon_refresh, 'refresh');
        addEvent(retr, 'mousedown', function() { sources_retract(uri); });
        addEvent(refr, 'mousedown', function() { sources_refresh(uri); });
        //retr.setAttribute('onmousedown', 'sources_retract("'+uri+'")');
        //refr.setAttribute('onmousedown', 'sources_refresh("'+uri+'")');
        actions.appendChild(retr);
        actions.appendChild(refr);
        addendum.appendChild(actions);
        
        x.appendChild(addendum);
    }
} //sources_add_html
/** remove a row from the sources table **/
function sources_remove_html(index)
{
    var row = document.getElementById('source'+index);
    if (!row) {
        twarn("no such source: " + index);
        return;
    }
    row.parentNode.removeChild(row);
} //sources_remove_html

/** update the sources table **/
function sources_update_html(uri, status) {
    var mystate = sources.status[uri].state, myindex = sources.status[uri].number;
    var x = document.getElementById('source'+myindex);
    if (!x)
        alert("What? no source"+myindex);
    else
        x.setAttribute('class', mystate);
    var td = x.childNodes[1];
    var tn = td.childNodes[0];
    tinfo("td="+td+" tn="+tn+" response="+status);
    td.replaceChild(document.createTextNode(status), tn);
} //sources_update_html

//ends
