// *********************************************************************
// 
// CoFindeR is free software; you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 3, or (at your option)
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see
// <http://www.gnu.org/licenses/>.
//
// COPYRIGHT 
//
// 2009, Christian Glahn and CELSTEC, Open University of the
// Netherlands
//
// *********************************************************************

// Interface: ComponentCrypt
//
// ComponentCrypt provides an abstraction to Mozilla's nsICryptoHash
// class. It eases the creation of MD5 hexadecimal hash strings from
// normal datastrings.
//
// ComponentCrypt should be part of the normal jetpack interfaces.
//
// ComponentCrypt is used by CoFindeR to create location identifiers
// for delicious.com. These identifiers are required to load tagging
// information for a location from the social bookmarking service.
//
ComponentCrypt = function() {
    this.crypt = Components.classes['@mozilla.org/security/hash;1'].createInstance(Components.interfaces.nsICryptoHash);
};

ComponentCrypt.prototype = {
    toHexString: function(hash) {
	// @hash: byte code of a fingerprint hash
	// returns an ordinary String object
	// 
	// toHexString() transforms the bytecode finger print (the
	// hash) into a hex string. Each byte of the fingerprint is
	// translated into two characters of hexadecimal digits (0-9,
	// a-f). 
	//
	function toBasedCode(charCode) {
	    return ("0" + charCode.toString(16)).slice(-2);
	}
	return [toBasedCode(hash.charCodeAt(i)) for (i in hash)].join(""); 			
    },
    
    MD5Hash: function(str) {
	// @str: an ordinary String object. 
	// returns a bytecode fingerprint @hash
	//
	// MD5Hash() creates a MD5 finger print of the string that was
	// passed to the function. 
	//
	var arr = [str.charCodeAt(i) for (i in str)]; 
	this.crypt.init(this.crypt.MD5); 
	this.crypt.update(arr, arr.length); 
	return this.crypt.finish(false);	
    },

    MD5HexHash: function(str) {
	// @str: an ordinary String object
	// returns an ordinary String object
	//
	// MD5HexHash() is a shortcut for toHexString(MD5Hash). 
	var hash = this.MD5Hash(str);
	return this.toHexString(hash);			
    }		
};

// Abstract CoFindeR object
var CoFindeR = {};

// Interface: CoFindeR.Core
//
// CoFindeR.Core is the core interface of CoFindeR. It is responsible
// to load and cache social tagging information and VLE keywords from
// the remote systems. The Core triggers the specific UI functions.
//
CoFindeR.Core = function() {
	var _this = this;
	

	// helper for delicious. The object is created only once.
	console.log("instanciate ComponentCrypt");
	this.crypt = new ComponentCrypt();

	// load the CoFindeR configuration
	// initialise the CoFindeR UI

	// hook CoFindeR into the jetpack tab API. onFocus is
	// triggered if a tab is selected. onReady is triggered if a
	// new page is loaded in a tab.
	console.log("setup callback functions");
	jetpack.tabs.onFocus(function(){ _this.testCurrentLocation();} );
	jetpack.tabs.onReady(function(){ _this.testCurrentLocation();} );

	// create UI widgets
	this.initStatusBar();

	console.log("CoFinder installed");
};

CoFindeR.Core.prototype = {
    // the micro cache is used to track if tagging
    // information has been recently loaded for a location. 
    // 
    // Currently, the MicroCache holds all data that has been loaded
    // from remote sytems. A future version should use the local
    // storage for this task. MicroCache will then only contain
    // information, if data for a location has been loaded from the
    // remote systems.
    MicroCache: {},

    // Constants for the external sources (should be loaded from the
    // configuration)
    moodlebase: null,
    delibase: 'http://feeds.delicious.com/v2/json/urlinfo/',
    active: false,
        
    // Method: testCurrentLocation
    //
    // no parameters 
    //
    // testCurrentLocation() gets the URL of the active tab and
    // triggers the CoFindeR data processing
    // 
    // This function is the glue to the jetpack tabs events
    //
    testCurrentLocation: function() {
	if ( this.active ) {
	    var href = jetpack.tabs.focused.url;
	    this.loadAllData(href);
	}
    },

    // Method: loadAllData
    //
    // @href: the requested location
    //
    // loadAllData() collects metadata for the requested location
    // from all remote systems. The method is aware if data is
    // already present in the local datastore. If the metadata has
    // been loaded during the current session, it will not be
    // requested from the remote system.
    //
    // if loadAllData() finds pre-existing information for the
    // requested location in the local datastore, it will load
    // this information before it tries to update the local
    // information with the data from the remote systems.
    //
    loadAllData: function(href) {
	if ( this.testDataComplete(href) ) {
	    // the data is already loaded, we will not bother loading
	    // it again
	    this.displayData(href);
	}
	else {
	    if ( !this.MicroCache[href] ) {
		// we visit the page the first time in this session
		this.MicroCache[href] = {};

		// test if there is page information about this page
		// already in the local storage and display what we have

		// load the most recent information from the remote systems
		// 1. load information from VLE
		// 2. load information from social tagging systems
		this.loadDeliciousData(href);
	    }
	    // else do nothing, because loading is already triggered
	}
    },

    // Function: testDataComplete
    //
    // @href: the location to be tested
    // returns a boolean @testvalue
    //
    // testDataComplete() checks if the minimum information
    // dataset is complete for the given @href. If information is
    // missing the function returns 0 (false). If the dataset is
    // complete, the function returns 1 (true).
    //
    testDataComplete: function(href) {	
	if ( this.MicroCache[href] ) {
	    // test VLE data

	    // test CI data
	    if ( !this.MicroCache[href]['delicious'] ) {
		return 0;
	    }
	    return 1;
	} 
    },
    
    // Method: loadDeliciousData
    //
    // @href: the requested location
    // 
    // this function loads the social tags for the requested
    // location from delicious.com. It creates an asynchroneous
    // HTTP request to delicious that is forwarded to the
    // store_and_display() method.
    //
    loadDeliciousData: function(href) {	
	var _this = this;
	var _href = href;

	var hash = this.crypt.MD5HexHash(href);

	$.get(this.delibase + hash, {}, // URL and optional parameters 
	      function(data) {
		  _this.store_and_display('delicious', _href, data);
	      },
	      "json" );		       // data format 						
    },

    // Method: store_and_display
    //
    // @source: originator of the data that is provided
    // @href:   the location that is described by the data
    // @data:   the data that is provided by the source
    //
    // store_and_display() is called whenever data from a remote
    // system becomes available. It stores the fresh data into the
    // local datastore and checks if the user still focuses on the
    // same page. In this case store_and_display() triggers UI
    // rendering.
    //
    store_and_display: function(source, href, data) {
 
	// always remember the data
	this.storeHrefData(source, href, data);
	
	// test if the user still focuses the same page
	var aHref = jetpack.tabs.focused.url;
	if (aHref == href && this.testDataComplete(href) ) {
	    this.displayData(href);
	}
    },
    
    // Method: storeHrefData
    //
    // @source: originator of the data that is provided
    // @href:   the location that is described by the data
    // @data:   the data that is provided by the source
    // 
    // storeHrefData() injects the metadata from a remote system
    // about a location into the local datastore. This method also
    // keeps track of updating the MicroCache in order to avoid
    // unnecessary overhead.
    //
    storeHrefData: function(source, href, data) {
	this.MicroCache[href][source] = data;
	// also attach the data to the local store
    },

    // Method: displayData
    //
    // @href: requested location
    //
    // displayData() triggers the UI function for displaying the
    // tagging information for the requested
    // location. displayData() preprocesses the metadata from the
    // different systems and passes only information that should
    // be visible to the user. If no information is available for
    // the requested location, displayData() hides the CoFindeR
    // UI.
    //
    displayData: function(href) {
	// get social tags for the location
	var tags = this.extractDeliciousTags(href);

	// get vle sharing information for the location
	// if the location is not shared 
	// get vle keywords of the user
	// and test the tags

	// only display if there is something to show
	if ( tags && tags.length ) {
	    // this should trigger the CoFindeR.UI object
	    jetpack.notifications.show(tags.join(', '));
	}
	else {
	    // Cofinder.UI,hide();
	}
    },

    // Function: extractDeliciousTags
    //
    // @href: the requested location 
    // returns an @tags array
    //
    // extractDeliciousTags() filters the tags that are associated
    // to the requested location in delicious. From the raw
    // dataset that is returned from delicious and is kept in the
    // local datastore. The function returns an array that
    // contains the associated tags. Additional information about
    // the tags is not available.
    //
    // If the delicous data is not yet available for the requested
    // location, extractDeliciousTags() returns null. If no tags
    // were assigned to the location, the function returns an
    // empty array.
    //
    extractDeliciousTags: function(href) {
	var retval = null;
	if ( this.MicroCache[href] && 
	     this.MicroCache[href].delicious ) {
	    var data = this.MicroCache[href].delicious;	    
	    if ( data && 
		 data[0] && 
		 data[0].top_tags ) {
		retval = [];
		for ( var t in data[0].top_tags ) {
		    retval.push(t);
		}
	    }
	}
	return retval;
    },


    // Method: initStatusBar()
    // 
    // creates a clickable area in the browser status bar. This area
    // allows a user to toggle CoFindeR. If CoFindeR is off, then no
    // information about the visited web-pages is checked and
    // provided.
    initStatusBar: function() {
	var _this = this;
	
	// load the default status from the configuration
	
	var objCSS = {}; 
	objCSS['font-weight']= this.active ? 'bold':'normal';
	objCSS['cursor']= 'pointer';

	
	jetpack.statusBar.append({
		html: '<div id="cofinder">CoFindeR</div>',
		onReady: function(widget) {
		    console.log('init cofinder ui');
		    $('#confinder',widget).css(objCSS);
		    $(widget).click(function(){_this.toggleTesting(widget);});
		}
	    } );
    },

    // Method: toggleTesting
    //
    // @widget: the statusbar widget
    //
    // toggleTesting activates or deactivates CoFindeR. This method is
    // triggerd through UI events.
    toggleTesting: function(w) {
	var objCSS = {}; 
	console.log('toggle CoFindeR');
	this.active = !this.active;
	objCSS['font-weight']= this.active ? 'bold':'normal';
	$('#cofinder',w).css(objCSS);

	if ( this.active ) {
	    this.testCurrentLocation();
	}
    },
};

// Runtime: CoFindeR.Instance
//
// CoFindeR.Instance is glues an instance of the CoFindeR.Core to a
// jetpack runtime.
//
CoFindeR.Instance = new CoFindeR.Core();
