/**
* Utility functions for tabulator
 */

if (typeof isExtension == 'undefined') isExtension = false; // stand-alone library

function string_startswith(str, pref) { // missing library routines
    return (str.slice(0, pref.length) == pref);
}

/**
* @class A utility class
 */

Util = {
    /** A simple debugging function */         
    'output': function (o) {
	    var k = document.createElement('div')
	    k.textContent = o
	    document.body.appendChild(k)
	},
    /**
    * A standard way to add callback functionality to an object
     **
     ** Callback functions are indexed by a 'hook' string.
     **
     ** They return true if they want to be called again.
     **
     */
    'callbackify': function (obj,callbacks) {
	    obj.callbacks = {}
	    for (var x=callbacks.length-1; x>=0; x--) {
            obj.callbacks[callbacks[x]] = []
	    }
	    
	    obj.addHook = function (hook) {
            if (!obj.callbacks[hook]) { obj.callbacks[hook] = [] }
	    }
        
	    obj.addCallback = function (hook, func) {
            obj.callbacks[hook].push(func)
	    }
        
        obj.removeCallback = function (hook, funcName) {
            for (var i=0;i<obj.callbacks[hook].length;i++){
                //alert(obj.callbacks[hook][i].name);
                if (obj.callbacks[hook][i].name==funcName){
                    
                    obj.callbacks[hook].splice(i,1);
                    return true;
                }
            }
            return false; 
        }
        obj.insertCallback=function (hook,func){
            obj.callbacks[hook].unshift(func);
        }
	    obj.fireCallbacks = function (hook, args) {
            var newCallbacks = []
            var replaceCallbacks = []
            var len = obj.callbacks[hook].length
            //	    tabulator.log.info('!@$ Firing '+hook+' call back with length'+len);
            for (var x=len-1; x>=0; x--) {
                //		    tabulator.log.info('@@ Firing '+hook+' callback '+ obj.callbacks[hook][x])
                if (obj.callbacks[hook][x].apply(obj,args)) {
                    newCallbacks.push(obj.callbacks[hook][x])
                }
            }
            
            for (var x=newCallbacks.length-1; x>=0; x--) {
                replaceCallbacks.push(newCallbacks[x])
            }
            
            for (var x=len; x<obj.callbacks[hook].length; x++) {
                replaceCallbacks.push(obj.callbacks[hook][x])
            }
            
            obj.callbacks[hook] = replaceCallbacks
	    }
	},
    
    /**
    * A standard way to create XMLHttpRequest objects
     */
	'XMLHTTPFactory': function () {
        if (isExtension) {
            return Components.
            classes["@mozilla.org/xmlextras/xmlhttprequest;1"].
            createInstance().QueryInterface(Components.interfaces.nsIXMLHttpRequest);
        } else if (window.XMLHttpRequest) {
            try {
                return new XMLHttpRequest()
            } catch (e) {
                return false
            }
	    }
	    else if (window.ActiveXObject) {
            try {
                return new ActiveXObject("Msxml2.XMLHTTP")
            } catch (e) {
                try {
                    return new ActiveXObject("Microsoft.XMLHTTP")
                } catch (e) {
                    return false
                }
            }
	    }
	    else {
            return false
	    }
	},
    /**
    * Returns a hash of headers and values
     */
	'getHTTPHeaders': function (xhr) {
	    var lines = xhr.getAllResponseHeaders().split("\n")
	    var headers = {}
	    var last = undefined
	    for (var x=0; x<lines.length; x++) {
            if (lines[x].length > 0) {
                var pair = lines[x].split(': ')
                if (typeof pair[1] == "undefined") { // continuation
                    headers[last] += "\n"+pair[0]
                } else {
                    last = pair[0].toLowerCase()
                    headers[last] = pair[1]
                }
            }
	    }
	    return headers
	},
    
    'dtstamp': function () {
	    var now = new Date();
	    var year  = now.getYear() + 1900;
	    var month = now.getMonth() + 1;
	    var day  = now.getDate() + 1;
	    var hour = now.getUTCHours();
	    var minute = now.getUTCMinutes();
	    var second = now.getSeconds();
	    if (month < 10) month = "0" + month;
	    if (day < 10) day = "0" + day;
	    if (hour < 10) hour = "0" + hour;
	    if (minute < 10) minute = "0" + minute;
	    if (second < 10) second = "0" + second;
	    return year + "-" + month + "-" + day + "T"
            + hour + ":" + minute + ":" + second + "Z";
	},
    
    'enablePrivilege': ((typeof netscape != 'undefined') && netscape.security.PrivilegeManager.enablePrivilege) || function() { return; },
    'disablePrivilege': ((typeof netscape != 'undefined') && netscape.security.PrivilegeManager.disablePrivilege) || function() { return; }
}

//////////////////////String Utility
Util.string = {
    //C++, python style %s -> subs
    'template': function(base, subs){
        var baseA = base.split("%s");
        var result = "";
        for (var i=0;i<subs.length;i++){
            subs[i] += '';
            result += baseA[i] + subs[i];
        }
        return result + baseA.slice(subs.length).join(); 
    }
};

//////////////////////Images Utility
if (Util.image) throw "Util.image has already been defined.";
Util.image = {};

Util.image.AJARImage = function AJARImage(src, alt, tt) {
  if (!tt && Icon.tooltips[src]) tt = Icon.tooltips[src];
  //var sp = document.createElement('span')
  var image = content.document.createElement('img');
  image.setAttribute('src', src);
  if (typeof alt != 'undefined') image.setAttribute('alt', alt);
  if (typeof tt != 'undefined') image.setAttribute('title',tt);
  return image;
};

/* This generates an interactive 'image' object as explained clearly in HTML5
 * spec <http://dev.w3.org/html5/spec/Overview.html#the-object-element> .
 * The reason why I (Kenny) put this in is because Firfox <img> elements does 
 * not allows SVG and I want to display SVG for foaf:img. See firefox issue
 * <https://bugzilla.mozilla.org/show_bug.cgi?id=276431> for discussion and 
 * example. Notice that indeed HTML5 does say <img> allows non-interactive 
 * SVG <http://dev.w3.org/html5/spec/Overview.html#the-img-element>.

 Therefore, outline.js:VIEWAS_image now calls this function, but when this 
 issue is fixed we might turn back to the function above 
*/
Util.image.AJARInteractive_Image = function AJARInteractive_Image(src, alt, tt){
  var image = content.document.createElement('object');
  image.setAttribute('data', src);
  image.setAttribute('style', "overflow: hidden;"); // a weird bug
  //For SVG objects the box seems big enough to display the whole picture, but
  //strangely the scroll bars appear. If you zoom in and zoom out, the scroll
  //bar disappear. I assume this is a Firefox bug and setting this style avoids
  //the scroll bars.
  
  if (typeof alt != 'undefined') 
    image.appendChild(content.document.createTextNode(alt));
  return image;
};

//================================================
function findLabelSubProperties() {
    var i,n
    tabulator.log.debug("rdfs:label subproperties:");
    var labelPredicates = kb.each(undefined, tabulator.ns.rdfs('subPropertyOf'), tabulator.ns.rdfs('label'));
    for (i=0, n=labelPredicates.length; i<n; i++) {
        if (labelPriority[labelPredicates[i].uri] == null) {
            labelPriority[labelPredicates[i].uri] = 1
        }
        tabulator.log.debug("rdfs:label subproperty "+ labelPredicates[i]);
        
    }
}

function label(x, trimSlash) { // x is an object
    /*
     var i,n
     var plist = kb.statementsMatching(x);
     var y, best = 0, lab = ""
     findLabelSubProperties();	// Too slow? Could cache somewhere
     for (i=0, n=plist.length; i<n; i++) {
         var st = plist[i]
         y = labelPriority[st.predicate.uri]
         if (st.object.value && st.object.lang) {
             if (st.object.lang.indexOf(LanguagePreference) >= 0) { y += 1 } // Best
             else { y -= 1}  // Prefer no lang to wrong lang (?)
         }
         if (y && (y > best) && (st.object.termType=='literal')) {
             lab = st.object.value
             best = y
         }
     }
     if (lab) {return lab};
     */
    
    function cleanUp(s1) {
        var s2 = "";
        for (var i=0; i<s1.length; i++) {
            if (s1[i] == '_' || s1[i] == '-') {
                s2 += " ";
                continue;
            }
            s2 += s1[i];
            if (i+1 < s1.length && 
                s1[i].toUpperCase() != s1[i] &&
                s1[i+1].toLowerCase() != s1[i+1]) {
                s2 += " ";
            }
        }
        if (s2.slice(0,4) == 'has ') s2 = s2.slice(4);
        return s2;
    }
    
    var lab=lb.label(x);
    if (lab) return lab.value;
    //load #foo to Labeler?
    
    if (x.termType == 'bnode') {
        return "...";
    }
    if (x.termType=='collection'){
        return '(' + x.elements.length + ')';
    }
    var s = x.uri;
    if (s.slice(-5) == '#this') s = s.slice(0,-5)
    else if (s.slice(-3) == '#me') s = s.slice(0,-3);
    
    var hash = s.indexOf("#")
    if (hash >=0) return cleanUp(s.slice(hash+1));

    if (s.slice(-9) == '/foaf.rdf') s = s.slice(0,-9)
    else if (s.slice(-5) == '/foaf') s = s.slice(0,-5);
    
    if (trimSlash) { // only trim URIs, not rdfs:labels
        var slash = s.lastIndexOf("/");
        if ((slash >=0) && (slash < x.uri.length)) return cleanUp(s.slice(slash+1));
    }
    return decodeURIComponent(x.uri)
}

function escapeForXML(str) {
    return str.replace(/&/g, '&amp;').replace(/</g, '&lt;')
}

//  As above but escaped for XML and chopped of contains a slash
function labelForXML(x) {
    return escapeForXML(label(x, true));
}

// As above but for predicate, possibly inverse
function predicateLabelForXML(p, inverse) {
    var lab;
    if (inverse) { // If we know an inverse predicate, use its label
	var ip = kb.any(p, tabulator.ns.owl('inverseOf'));
	if (!ip) ip = kb.any(undefined, tabulator.ns.owl('inverseOf'), p);
	if (ip) return labelForXML(ip)
    }
        
    lab = labelForXML(p)
        if (inverse) {
            if (lab =='type') return '...'; // Not "is type of"
            return "is "+lab+" of";
        }
    return lab
} 
//=====================================
///////////////// Utility

function emptyNode(node) {
    var nodes = node.childNodes, len = nodes.length, i
    for (i=len-1; i>=0; i--) node.removeChild(nodes[i])
        return node
}

function ArrayContains(a, x) {
    var i, n = a.length
    for (i=0; i<n; i++)
        if (a[i] == x) return true;
    return false
}

/** returns true if argument is an array **/
function isArray(arr) {
    if (typeof arr == 'object') {  
        var criterion = arr.constructor.toString().match(/array/i); 
        return (criterion != null); 
    }
    return false;
} //isArray

/** turns an HTMLCollection into an Array. Why? um, mostly so map & filter will work, though
* i suspect they work on collections too **/
function HTMLCollection_to_Array(coll) {
    var arr = new Array();
    var len = coll.length;
    for (var e = 0; e < len; e++) arr[e] = coll[e];
    return arr;
} //HTMLCollection_to_Array

/** evaluate expression asynchronously. scoping? **/
function acall(expr) {
    setTimeout(expr, 0); //start off expr
} //acall

function AJAR_handleNewTerm(kb, p, requestedBy) {
    if (p.termType != 'symbol') return;
    var docuri = Util.uri.docpart(p.uri);
    var fixuri;
    if (p.uri.indexOf('#') < 0) { // No hash
        
        // @@ major hack for dbpedia Categories, which spred indefinitely
        if (string_startswith(p.uri, 'http://dbpedia.org/resource/Category:')) return;  
        
        /*
         if (string_startswith(p.uri, 'http://xmlns.com/foaf/0.1/')) {
             fixuri = "http://dig.csail.mit.edu/2005/ajar/ajaw/test/foaf"
                            // should give HTTP 303 to ontology -- now is :-)
        } else
         */
        if (string_startswith(p.uri, 'http://purl.org/dc/elements/1.1/')
            || string_startswith(p.uri, 'http://purl.org/dc/terms/')) {
            fixuri = "http://dublincore.org/2005/06/13/dcq";
                           //dc fetched multiple times
        } else if (string_startswith(p.uri, 'http://xmlns.com/wot/0.1/')) {
            fixuri = "http://xmlns.com/wot/0.1/index.rdf";
        } else if (string_startswith(p.uri, 'http://web.resource.org/cc/')) {
                                                  //            tabulator.log.warn("creative commons links to html instead of rdf. doesn't seem to content-negotiate.");
            fixuri = "http://web.resource.org/cc/schema.rdf";
        }
    }
    if (fixuri) {
        docuri = fixuri
    }
    if (sf.getState(docuri) != 'unrequested') return;
    
    if (fixuri) {   // only give warning once: else happens too often
        tabulator.log.warn("Assuming server still broken, faking redirect of <" + p.uri +
                           "> to <" + docuri + ">")	
    }
    sf.requestURI(docuri, requestedBy);
} //AJAR_handleNewTerm

function myFetcher(x, requestedBy) {
    //tabulator.log.test('Entered myFetcher');
    if (x == null) {
        tabulator.log.debug("@@ SHOULD SYNC NOW") // what does this mean?
    } else {
        tabulator.log.debug("Fetcher: "+x)
        //tabulator.log.test('Fetcher: ' + x);
        AJAR_handleNewTerm(kb, x, requestedBy)
    }
}

function getTarget(e) {
    var target
    if (!e) var e = window.event
        if (e.target) target = e.target
            else if (e.srcElement) target = e.srcElement
                if (target.nodeType == 3) // defeat Safari bug [sic]
                    target = target.parentNode
                        tabulator.log.debug("Click on: " + target.tagName)
                        return target
}

//////////////////////////////////// User Interface Events

function getAboutLevel(target) {
    var level
    for (level = target; level; level = level.parentNode) {
        tabulator.log.debug("Level "+level)
        var aa = level.getAttribute('about')
        if (aa) return level
    }
        return undefined
}

function ancestor(target, tagName) {
    var level
    for (level = target; level; level = level.parentNode) {
        // tabulator.log.debug("looking for "+tagName+" Level: "+level+" "+level.tagName)
        if (level.tagName == tagName) return level;
    }
    return undefined
}

function getAbout(kb, target) {
    var level, aa
    for (level=target; level && (level.nodeType==1); level=level.parentNode) {
        tabulator.log.debug("Level "+level + ' '+level.nodeType)
        aa = level.getAttribute('about')
        if (aa) 
            return kb.fromNT(aa)
                else if (level.tagName=='TR') //this is to prevent literals passing through
                    return undefined
    }
        return undefined
}

function getTerm(target){
    var statementTr=target.parentNode;
    var st=statementTr.AJAR_statement;

    var className=st?target.className:'';//if no st then it's necessary to use getAbout    
    switch (className){
        case 'pred':
        case 'pred selected':
            return st.predicate;
            break;
        case 'obj':
        case 'obj selected':
            if (!statementTr.AJAR_inverse)
                return st.object;
            else
                return st.subject;
            break;
        case '':    
        case 'selected': //header TD
            return getAbout(kb,target); //kb to be changed
        case 'undetermined selected':
            return (target.nextSibling)?st.predicate:((!statementTr.AJAR_inverse)?st.object:st.subject);
    }
}
//////////////////////////////////////Source Utility

/**This is for .js that is not so important**/
function include(linkstr){
    
    var lnk = document.createElement('script');
    lnk.setAttribute('type', 'text/javascript');
    lnk.setAttribute('src', linkstr);
    //TODO:This needs to be fixed or no longer used.
    //document.getElementsByTagName('head')[0].appendChild(lnk);
    return lnk;
}

function addLoadEvent(func) {
    var oldonload = window.onload;
    if (typeof window.onload != 'function') {
        window.onload = func;
    }
    else {
        window.onload = function() {
            oldonload();
            func();
        }
    }
} //addLoadEvent

//////////////////////////////////View Utility
function findPos(obj) { //C&P from http://www.quirksmode.org/js/findpos.html
    var myDocument=obj.ownerDocument;
    var DocBox=myDocument.getBoxObjectFor(myDocument.documentElement);
    var box=myDocument.getBoxObjectFor(obj);
    return [box.screenX-DocBox.screenX,box.screenY-DocBox.screenY];
	/*
	var curleft = curtop = 0;
	if (obj.offsetParent) {
		curleft = obj.offsetLeft
		curtop = obj.offsetTop
		while (obj = obj.offsetParent) {
			curleft += obj.offsetLeft
			curtop += obj.offsetTop
		}
	}
	return [curleft,curtop];
	*/
}

function getEyeFocus(element,instantly,isBottom,myWindow) {
    if (!myWindow) myWindow=window;
    var elementPosY=findPos(element)[1];
    var totalScroll=elementPosY-52-myWindow.scrollY; //magic number 52 for web-based version
    if (instantly){
        if (isBottom){
            myWindow.scrollBy(0,elementPosY+element.clientHeight-(myWindow.scrollY+myWindow.innerHeight));
            return;
        }            
        myWindow.scrollBy(0,totalScroll);
        return;
    }
    var id=myWindow.setInterval(scrollAmount,50);
    var times=0
        function scrollAmount(){
            myWindow.scrollBy(0,totalScroll/10);
            times++;
            if (times==10)
                myWindow.clearInterval(id);
        }
}

function AJARImage(src, alt, tt, doc) {
	if(!doc)
	    doc=document;
    if (!tt && Icon.tooltips[src])
        tt = Icon.tooltips[src];
    var image = doc.createElement('img');
    image.setAttribute('src', src);
//    if (typeof alt != 'undefined')      // Messes up cut-and-paste of text
//        image.setAttribute('alt', alt);
    if (typeof tt != 'undefined')
        image.setAttribute('title',tt);
    return image;
}

//returns an array containing x1,y1,x2,y2,etc for some uri
//http://blah.org/f.html?x1=y1&x2=y2, where results[x1]=y1
function getURIQueryParameters(uri){
    var results =new Array();
    var getDataString=new String(window.location);
    var questionMarkLocation=getDataString.search(/\?/);
    if (questionMarkLocation!=-1){
        getDataString=getDataString.substr(questionMarkLocation+1);
        var getDataArray=getDataString.split(/&/g);
        for (var i=0;i<getDataArray.length;i++){
            var nameValuePair=getDataArray[i].split(/=/);
            results[decodeURIComponent(nameValuePair[0])]=decodeURIComponent(nameValuePair[1]);
        }
    }
    return results;
}

function parse_headers(headers) {
    var lines = headers.split('\n');
    var headers = {};
    for (var i=0; i < lines.length; i++) {
        var line = webdav._strip(lines[i]);
        if (line.length == 0) {
            continue;
        }
        var chunks = line.split(':');
        var hkey = webdav._strip(chunks.shift()).toLowerCase();
        var hval = webdav._strip(chunks.join(':'));
        if (headers[hkey] !== undefined) {
            headers[hkey].push(hval);
        } else {
            headers[hkey] = [hval];
        }
    }
    return headers;
}

///////////docuemnt utility
Util.document={
    'split': function(doc, number){
        var result = [doc];
        var docRoot = doc.documentElement;
        var nodeNumber = docRoot.childNodes.length;
        var dparser = Components.classes["@mozilla.org/xmlextras/domparser;1"]
                    .getService(Components.interfaces.nsIDOMParser);
        var division = (nodeNumber - nodeNumber%number)/number;
        for (var i=0;i< number-1;i++){
            var newDoc = dparser.parseFromString('<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"></rdf:RDF>','application/xml');                        
            for (var j=0;j<division;j++)
                newDoc.documentElement.appendChild(newDoc.adoptNode(docRoot.firstChild));
            result.push(newDoc);
        }
        return result;    
    }
}
