/*
           ,---------------------------------------------------------.
          /  tsrkit library functions. All the JavaScript functions   )
         /   and objects are in this file.                           /
        /                                                           /
       (            Ioan Sameli, TSR, 2006 - 2009                  /
        `--\   /--------------------------------------------------´
           |  /
            \|
             `
           (")-''-(").___,.~-´'^`-._
            \O_ O  )   `-.  (     ).`-.__.´)
            (_Y_.)/  ._   )  `._ `. `´-..-´
          _,.`--´_,.-_/  /~-`_.´ ,´
         (il).-'´  (li).´  ((!.-´

            _________________________________________________
    _______|                                                 |_______
    \      |   For more informations about all this stuff,   |      /
     \     |   check the documentation on tsrwiki.           |     /
     /     |_________________________________________________|     \
    /_________)                                            (________\


Code convention used in this project: http://javascript.crockford.com/code.html

TODO: an invalid xml ajax answer seems to give "no result" instead of an error

TODO: replace innerHTML with nodeValue, it's better because it's less worse (and it's valid)

TODO1: Since this won't only be used for videos, replace all the occurences to "video" by "item",
       and "playlist" by "search", or something like this

TODO firstly : translate the doc in french, for the french developpers of the TSR

*/

var debugLevel = 1;

if (typeof $ == 'undefined') $ = function(r){return document.getElementById(r)};

  /********************************/
 /***   tsrkit configuration   ***/
/********************************/

	if (!fileExtension) var fileExtension = '/scripts/video/tsrkit_extension.js?d=2009-03-11';

	// The delay after wich a remote request is considered as timed out, in milliseconds
	var delayTimeoutRequest = 10000;

	// Default HTML template for the playlists (ajax)
	var defaultSearchResultTemplate = '<li class="line$line">'
		 + '<a href="#" name="$id" rel="video" onclick="return $player.playVideo(this.name);">'
		 + '$img<span class="title">$title</span></a></li>';

	// The title of a video, basically it's just the title, but the date or other details could eventually be added here
	var titleTemplate = '$title';
	
	// ID of the default target HTML element for the results of Data loading (Ajax)
	var defaultSearchTarget = null;

	// ID of the default target HTML element for the video Playback
	var defaultVideoTarget = null;

	// The URL template for the search
	var searchTemplateURL = 'index.html?descending=true&siteSect=500202&language=fre&searchNow=true&searchString=$s';

	// Ajax waiting signs, put whatever you want here
	var ajaxLoadingSign = '<div class="ajaxLoading">Chargement en cours...</div>';
	var ajaxErrorSign = '<div class="errMsg">Un problème est survenu, nous ne pouvons malheureusement pas répondre à votre requête.</div>';
	var ajaxEmptySign = '<div class="errMsg">La recherche n\'a donn&eacute; aucun r&eacute;sultat</div>';
	var requestTimeoutSign = '<div style="font-weight:bold;">Probl&egrave;mes r&eacute;seaux temporaires.<br><br>Veuillez r&eacute;essayer plus tard.</div>';

	// Used when the playback of a video fails because no compatible file has been found
	var noAvailableFile = '<div class="errMsg">Aucun fichier compatible n\'a &eacute;t&eacute; trouv&eacute;.</div>';

  /***************************/
 /***   Playback module   ***/
/***************************/

	// The player root object, contains all the videos and some settings
	// varName (optional, string): to inform the tsrkit object of its own name. Useful to use JSON, for callbacks
	// Ioan Sameli, 26.09.2006
	tsrkit = function(varName) {

		// Initialize the object before populating it
		this.videos = {};
		
		// autoplay
		this.autoPlay = true;
		this.videoWidth = 800;
		this.videoHeight = 450;

		// Same for requests
		this.requests = {};

		if (varName) this.varName = varName;

		// The ID of the HTML element where the data will be injected
		if (defaultVideoTarget) this.videoTarget = defaultVideoTarget;
		if (defaultSearchTarget) this.searchTarget = searchTarget;

		// The video that is currently being played
		this.currentVideoID = 0;

		// Get a user automatically
		// c (function): callback function when everything has been initialized
		this.init = function(c) {
			this.getUser(c);
			this.additionalPlayOps();
		}

		// Inject a new title in the player
		this.setTitle = function(pTitle) {
			var t = $(this.videoTitleTarget);
			if (t) t.innerHTML = pTitle;
		}

		// Pretty useless, but used for testing.
		this.getTitle = function() {
			var pTitle = $(this.videoTitleTarget).innerHTML;
			trace(pTitle);
			return pTitle;
		}
		
		this.setTitleAndDetail = function(v) {
			// Display the title(s) of this video
			var t = parseTemplate(titleTemplate, v);
			if (v.title) this.setTitle(t);
			//trace('play video: ' + v.title + ' ' + v.category + ' ' + this.programs, 'error');
			
			// And the detail, if set
			if (typeof detailTemplate != 'undefined' && detailTemplate && (el = $(this.videoDetailTarget))) {
				if (v.isMeta) el.style.display = 'none';
				else {
					el.style.display = 'block';
					el.innerHTML = parseTemplate(detailTemplate, v);
				}
			}
        }

		// Create a new search in this object, with Ajax or JSON
		// rSource (string): The url to load the data from
		// rTarget (string, optional): The HTML element to inject the data in
		this.getNewSearch = function(rSource, rTarget) {

			var newPlaylist = new tsrkit_search(rSource);

			// The playlist will need to know where it comes from.
			newPlaylist.linkToParent(this);

			// If rTarget isn't provided, use the player's default playlist
			newPlaylist.rTarget = rTarget || this.searchTarget;

			// Return the object, so the caller can play with it
			return newPlaylist;
		}

		// Add a video to the global video directory of this object, that needs to be filled later
		// idNewVideo (string): the ID of the video to add, will be an integer most of the time.
		this.addvideo = function(nvId) {

			if (!nvId) {
				nvId = this.videos.length;
				while ((typeof this.videos[nvId]) != 'undefined') nvId++;
			}

			return this.videos[nvId] = new tsrkit_video(nvId);
		}
		
		this.getVideo = function(id) {
            if (!id)
                id = this.currentVideoID;
            if (typeof this.videos[id] == 'object')
                return this.videos[id];
            return null;
        }

		// Play a video
		// First, get the right medium according to the preference, and then inject the HTML code to render it in the browser
		this.playVideo = function(idVideo) {
			// This video should now be the default video
			if (!idVideo) idVideo = this.currentVideoID;
			else this.currentVideoID = idVideo;

			if ('object' == typeof this.videos[idVideo]){
                var v = this.videos[idVideo];

				this.setTitleAndDetail(v);
				
				// Render the video and return the result
				return this.injectMedium();

			} else {
				trace('the video '+idVideo+' is not defined.','error');
				return false;
			}
		}

		// Returns the index of the highest flash media available
		this.selectFormat = function(idVideo, favCodec, favBitrate) {

			// Check if the video's media have been loaded first
			if ( (typeof this.videos[idVideo]) == 'undefined') {
				trace('selectFormat: The video ' + idVideo + ' is not loaded !', 'error');
				return false;
			}

			// We'll work on the media, so make a local var
			var media = this.videos[idVideo].media;

			var chosenFormat = null;
			var highestFound = 0;

			for (var i in media) {
				var currentMedium = media[i];
								
				// Ignore 3GPP / windowsMedia / realMedia				
				if(currentMedium.codec != '3GPP' && currentMedium.codec != 'windowsMedia' && currentMedium.codec != 'realMedia' && currentMedium.bitrate > highestFound) {
					highestFound = currentMedium.bitrate;
					chosenFormat = i;
				}
				
				if(currentMedium.codec == 'swf') chosenFormat = i;
				
			}

			if (chosenFormat != null) {
				// trace('Best format for video ' + idVideo + ' is '+media[chosenFormat].mFormat, 'success');
				return chosenFormat;
			} else {
				trace('No compatible format found for video ' + idVideo, 'error');
				return false;
			}
		}

		// Flush the video cache array, since it may get huge (thousand of items is the user browse through a lot of videos).
		// TODO: is this really useful ? Run some benchmarks
		this.resetVideoCache = function() {
			this.videos = {};
		}

		// Parse the dynamic links of a playlist to change them to static links to
		// the physical video file, according to the user preference
		// Should be called each time a user changes his preference, to update the links
		// The links to play a video should have a attribute rel="video", and the ID of
		// the video as ID, for example id="4894574"
		this.parsePlaylistLinks = function() {

			// Loop on all the links of the page that have a "rel" attribute set to "video"
			playlistLinks = document.getElementsByTagName('a');

			for (var i = 0; i < playlistLinks.length; i++) {

				if (playlistLinks[i].rel == 'video') {

					var idVideo = playlistLinks[i].name;

					if (!this.videos[idVideo]) {
						trace('parsePlaylistLinks: The video ' + idVideo + ' is not loaded.', 'error');
						continue;
					}

					// Get the best format for this video
					var chosenFormat = this.selectFormat(idVideo);

					// if (typeof chosenFormat == 'function') continue;
					if (!chosenFormat) continue;

					// Get the medium
					medium = this.videos[idVideo].media[chosenFormat];

					// Set the link href to the physical file
					if (medium) playlistLinks[i].href = medium.url;
				}
			}
		}

		// Will inject the HTML code to play the selected video in the element with the id given by idElementPlayer
		// idVideo (optional): the id of the video, it has to exist already in player.videos[x]
		// mFormat (optional): the string with the format of the medium we want to play, for example "realMedia-450".
		// If these arguments are not provided, the function will manage to get them by itself.
		// Ioan Sameli, 27.03.2006
		this.injectMedium = function(idVideo) {

			// If no idVideo is provided, play the default Video of the player
			if (!idVideo) idVideo = this.currentVideoID;

			// get the best format according to the player's settings
			mFormat = this.selectFormat(idVideo);

			// If somehow there's no available media for this video
			if (typeof mFormat != 'number' && typeof mFormat != 'string') {
				if ($(this.videoContainer)) {
					$(this.videoContainer).innerHTML = '<div id="'+this.videoTarget+'">' + parseTemplate(noAvailableFile, this.videos[idVideo]) + '</div>';
				}			
				this.additionalPlayOps(mFormat);
				TSR.player.stats.error(this.videoTarget, this.videos[idVideo], 'noflash');	
				return false;
			}

			var medium = this.videos[idVideo].media[mFormat];

			// Create the properties collection that will be used by the templating
			var mProps = {
				'title':    this.videos[idVideo].title,
				'file':     escape(medium.url),
				'codec':    medium.codec,
				'bitrate':  medium.bitrate,
				'width':    medium.width,
				'height':   medium.height,
				'video':    this.videos[idVideo]
			};
			
			switch (medium.codec) {

			case 'swf':

				swfobject.embedSWF(medium.url, this.videoTarget, "800", "450", "8.0.0");
								
				break;
				
			default:

				var p = TSR.player.init(this.videoTarget);
				
				TSR.player.addStream(this.videos[idVideo]);
				
				/*
                var v = this.videos[idVideo];
				for (var k in v) {
                    trace(k + ': ' + v[k]);
                }
                */
				// trace('<hr/><hr/><hr/>',0,3);
				//trace(this.videos[idVideo].toSource(),0,3);
				// trace('<h2>Voici exactement le tableau que j\'envoie &agrave; TSR.player:</h2> (pour "'+this.videos[idVideo].title+'")', 'info',3);
				
				p.options.width = this.videoWidth;
				p.options.height = this.videoHeight;
				p.options.autoPlay = this.autoPlay;
				p.play(idVideo);
				
				break;
			}
			
			// Extensions? Give the medium, in case it's needed
			this.additionalPlayOps(mFormat);

			return false;
		}
	}

	tsrkit_video = function(vId) {
	// Module to configure and play a video
	// Ioan Sameli, 26.09.2006
	// vId (string, optional): To give the ID of the video to play
	// With no configuration, the video will play according to the personal preference of the user
	// TODO: this should actually be something like a subclass of tsrkit

		if (vId) this.id = vId;
		
		this.hasBoutique = false;

		// method: Add a medium to the current video
		this.addMedium = function() {

			// If there isn't yet a parent media array for this video, just create one
			if (!this.media) this.media = new Array;

			// Create a new medium empty object and push it in the media array
			var newMedium = {};
			this.media.push(newMedium);

			return newMedium;
		}
	}

  /***********************/
 /***   Ajax module   ***/
/***********************/

	tsrkit_ajaxRequest = function(rSource) {
	// Ioan Sameli, 10.05.2006
	// Call an URL in ajax, and set the function that will handle the answer
	// rSource (string, optional): the URL to load the XML data from

		// If we want to open the request in async or not
		this.async = true;

		// I don't think POST will be very used, so...
		this.rMethod = 'GET';

		// If the source URL has been given in parameter
		if (rSource) this.rSource = rSource;

		// To inject HTML code into the target,
		// will replace the content by default, or add at the end if add is not null
		// TODO: rewrite using DOM ? But i'm injecting HTML code already
		// rResult (string, optional): Some text or HTML to inject in the target.
		// 			If null, will empty the target.
		// append (boolean, optional): If specified, will append the rResult to the target, otherwise will replace the content
		// sTarget (string, optional): to specify another target than the default rTarget's request property.
		this.injectToTarget = function(rResult, append, sTarget) {

			sTarget = sTarget || this.rTarget;

			if (!sTarget) return;

			if (e = $(sTarget)) {

				// If we want to hide the target and that the script.aculo.us effect library is available
				 if (!append && !rResult && typeof Effect == 'object') e.style.display = 'none';

				// For more readable generated code
				rResult += '\n';

				// Replace or append, according to the "append" param
				if (rResult) {
					if (append) e.innerHTML += rResult;
					else e.innerHTML = rResult;
				}
			}
			else trace('injectToTarget: Element ' + sTarget + ' not found \n(' + this.rSource + ')', 'error');
		}

		this.displayTarget = function(invert) {

			// TODO1: this function, together with injectToTarget, is a dirty mess... It should be rewritten.

			if (!this.rTarget) return;

			if (e = $(this.rTarget)) {
				if (typeof Effect == 'object') new Effect.Appear(e, {duration: 0.5});
				else e.style.display = 'block';
			}

			/*
			if (e = $(this.rTarget)) {

				// Check if script.aculo.us effect library is available
				if (typeof Effect == 'object') {
					if (invert) new Effect.Fade(e, {delay: 0.1, duration: 0.1, queue: {position: 'end', scope: 'switchPanel'}});
					else new Effect.Appear(e, {delay: 0.1, duration: 0.3, queue: {position: 'end', scope: 'switchPanel'}});
				} else {
					if (invert) e.style.display = 'none';
					else e.style.display = 'block';
				}
			}
			*/
		}

		// Link the current request to another object, so we'll be able to address the request
		// directly, useful for example with JSON async callback functions.
		// TODO2: maybe we should destroy the object once it's done, to avoid having many useless
		// expired properties in the parent (causing performance / memory problems ?)
		this.linkToParent = function(objParent) {

			// create a new property in the parent, with a unique ID based on the timestamp
			this.id = new Date().getTime();

			// Loop until we find a free property. Will actually be useless most
			// of the time, unless we launch more than one request in the same millisecond.
			while ((typeof objParent.requests[this.id]) != 'undefined') {
				this.id++;
			}

			// Add the current request object to the parent
			objParent.requests[this.id] = this;

			// And add the parent to the current request object
			this.parent = objParent;

			// Print debug
			var reLaunchLink = '<a href="javascript:' + objParent.varName + '.requests[' + this.id + '].launch();">request' + this.id + '</a>';
			trace('New request added to ' + objParent.varName + ': ' + reLaunchLink, 'success');
		}

		// Launch the request !
		// If the request URL is externam, use JSON, otherwise use classic Ajax
		this.launch = function() {

			// Check if the handleResult function is set before launching the request
			if ('function' == typeof this.handleResult) {

				// If the link is internal, use traditional Ajax method, otherwise JSON method
				if (testIfLinkIsInternal(this.rSource)) this.launchAjax();
				else this.launchJSON();

			}
			else trace('Ajaxrequest: no valid handleResult function ?', 'error');

		}

		// A method that will be called after a timeout if the request doesn't get any answer.
		this.handleRequestTimeout = function() {
			this.injectToTarget(requestTimeoutSign);
			this.displayTarget();
		}

		// This method will set the function that the JSON result will call.
		// It can be specified by the argument or generated automatically.
		// Will return the name of the function (string)
		this.setJSONCallbackFunctionName = function(nameFunction) {

			// If no function name, simply return the path to the handleJSONAnswer
			if (! nameFunction) return this.parent.varName + '.requests[' + this.id + '].handleJSONAnswer';

			// If a name has been specified in the argument, create a new function linked to the handleJSONAnswer
			var r = window[this.parent.varName].requests[this.id];
			window[nameFunction] = function(result){r.handleJSONAnswer(result);};
			return nameFunction;
		}

		// For crossdomain request, include a script that will contain JSON data with a callBack function
		this.launchJSON = function() {

			// TODO2: this is often injected into a <ul> html list, so it won't really be valid html
			if (!this.noLoadingSign) this.injectToTarget(ajaxLoadingSign);

			// "Calling" the JSON by adding a script
			trace('JSON: loading the script <a href="' + this.rSource + '">' + this.rSource + '</a>');
			loadScript(this.rSource, 'UTF-8');

			// Manage the timeout: call the timeout function with a timeout
			var p = this;
			window.setTimeout(function(){p.handleRequestTimeout();}, delayTimeoutRequest);

		}

		this.handleJSONAnswer = function(result) {

			// First, cancel the timeout warning, this is the answer !
			this.handleRequestTimeout = function(){};

			// And forward the result to the handleResult() function
			this.handleResult(result);
		}

		// To launch the ajax request !
		this.launchAjax = function() {

			// create the HTTP Object
			this.request = tsrkit_createXMLHttp();

			if (this.request) {

				// Debug
				trace('AJAX: open this url by ' + this.rMethod + ' <a href="' + this.rSource + '">' + this.rSource + '</a>', 'info');

				// TODO2: sometimes (always ?) this is injected in a <ul> html list and it's not valid code
				if (!this.noLoadingSign) this.injectToTarget(ajaxLoadingSign);

				// Create a new instance of the current object so it will be
				// available in the onreadystatechange subfunction
				var currentAjaxRequest = this;

				// Reset the starTime (that will be used to calculate the bandwidth),
				// if we use the same ajax object more than once
				currentAjaxRequest.starTime = 0;

				// Just for more clear and concise code
				var rObj = currentAjaxRequest.request;

				// Set the url and method of the request
				rObj.open(this.rMethod, this.rSource, this.async);

				// The callback subfunction
				// This embedded function will handle the answer of the http connection,
				// and call the fonction that will handle the result
				// Ioan Sameli, 10.05.2006
				rObj.onreadystatechange = function() {

					// When the answer is here, check if it's valid, and handle the result with "handleResult" function
					if (rObj.readyState == 4) {

						// First, cancel the timeout warning, this is the answer !
						currentAjaxRequest.handleRequestTimeout = function(){};

						// Calculate at what bandwidth the client loaded the answer
						var now = new Date().getTime();
						var time = now - currentAjaxRequest.starTime;
						if (!time) time = 1;
						var size = rObj.responseText.length;
						var speed = Math.round(8 * size / time)
						currentAjaxRequest.bandWidth = speed;

						// When the answer is valid
						if (rObj.responseText.indexOf('invalid') == -1) {

							// Print debug to console
							trace('AJAX: we got the answer at ' + currentAjaxRequest.bandWidth + 'Kbps ('
								+time + 'ms for ' + Math.round(size / 1024) + 'KB) !', 'success');

							// Handle the result, give the request as parameter
							currentAjaxRequest.handleResult(currentAjaxRequest);

							// If the JavaScript in the answer has to be parsed
							if (currentAjaxRequest.parseJS == true) {

								// This was on prototype
								// rObj.responseText.evalScripts();

								var s = rObj.responseXML.getElementsByTagName('script');
								for (var i = 0; i < s.length; i++) eval(s.item(i).firstChild.data);
							}

							// Finally, call the freaking callBack function !
							tryCallBack(currentAjaxRequest.rCallback);

						} else {
							trace('AJAX: invalid answer ! (' + currentAjaxRequest.bandWidth + 'Kbps)', 'error');
							currentAjaxRequest.injectToTarget(ajaxErrorSign);
						}
					}

					// readyState 3: the server answered and the datas are loading
					if (rObj.readyState == 3) {

						// When the answer starts to load, save the time, to calculate the bandwidth later
						if (!currentAjaxRequest.starTime) {
							var now = new Date().getTime();
							currentAjaxRequest.starTime = now;
							trace('AJAX: server answered, loading data', 'info');
						}
					}
				}

				// And finally, send the request
				rObj.send(null);

				// Manage the timeout: call the timeout function with a timeout
				var p = this;
				window.setTimeout(function(){p.handleRequestTimeout();}, delayTimeoutRequest);

				// return true if everything has been a success !
				return true;

			} else {
				trace('Ajax isn\'t available, it doesn\'t work.', 'error')
				return false;
			}
		}
	}


  /*************************/
 /***   Search module   ***/
/*************************/

	// All the playlist related functions
	// Ioan Sameli, 22.09.2006
	tsrkit_search = function(rSource) {

		// This class will use ajax methods
		this.inheritFrom = tsrkit_ajaxRequest;
		this.inheritFrom(rSource);

		// Default playlist HTML template
		this.rTemplate = defaultSearchResultTemplate;

		// arrayResults: an array that contains all the videos to display
		this.renderSearchResults = function(arrayResults, listHeader) {

			var completePlaylistHTML = '';

			listHeader = listHeader || '';

			var j = 0;

			// Loop on all the video that are in the array
			for (var i in arrayResults) {

				var currentVideo = arrayResults[i];

				if (typeof currentVideo != 'object') continue;

				// Add a new property, so the $player in the template will be replaced with the player's object name
				currentVideo.player = this.parent.varName;

				// Parse the template
				currentItemHTML = parseTemplate(this.rTemplate, currentVideo, j++);

				completePlaylistHTML += currentItemHTML;
			}

			// Somehow, if the playlist is not displayed with a small delay, the display will
			// bug when using script.aculo.us effect library. So use a setTimeout with no delay.
			var currentPlaylist = this;

			window.setTimeout(function(){

				// Finally, render the current item to the html list, with the listHeader
				// Or display something if no video has been found
				currentPlaylist.injectToTarget(completePlaylistHTML ? listHeader + completePlaylistHTML : ajaxEmptySign);

				// Redisplay the playlist once everything is done
				currentPlaylist.displayTarget();

				// Parse the links, to link to the real files
				currentPlaylist.parent.parsePlaylistLinks();

			},0);
		}
	}

  /********************************************/
 /***   Ahah: Loading of external panels   ***/
/********************************************/

	// Load an external file in a given HTML Element
	// rSource (string): the URL to load the XML data from
	// rTarget (string): the ID of the target element to inject the data in
	// initfunction(optional): function to execute once the panel has been loaded
	// Ioan Sameli, 25.09.2006
	// TODO: i'm not sure this function works with external URL (ajax will switch to JSON), to test
	tsrkit_ahahLoad = function(rSource, rTarget, initFunction) {

		// This class will use ajax methods
		this.inheritFrom = tsrkit_ajaxRequest;
		this.inheritFrom(rSource);

		// The target
		this.rTarget = rTarget;

		// If after loading the data we need to execute something
		this.initFunction = initFunction ? initFunction : false;

		var ahahCallback = this.initFunction;

		// Set to true to parse the JavaScript code fragments in the loaded document
		// Warning: If yes, the document loaded needs to be strictly valid XML !
		this.parseJS = true;

		this.handleResult = function(answer) {

			// If a target element is specified, insert the answer into target.
			// If no target has been specified, just append to the document, and as ID give the source URL
			// Strip the scripts if they've been parsed
			var fillTarget = answer.rTarget || answer.rSource;

			fillBlock(fillTarget, answer.request.responseText);

			// Call the callback
			tryCallBack(ahahCallback);

			// Extensions support
			// answer.additionalAhahOps();
		}

		// Launch immediatly
		// TODO: maybe do this a better way
		this.launch();
	}



  /********************************/
 /***   Configuration module   ***/
/********************************/


  /***********************************/
 /***   Random useful functions   ***/
/***********************************/

	// Just a cool little thing to easily extract data from XML
	function getXmlElement(elmRoot,elmName) {
		return elmRoot.getElementsByTagName(elmName).length ? (a = elmRoot.getElementsByTagName(elmName).item(0).firstChild) ? a.data : '' : '';
	}

	// To print something in the console, used from nearly everywhere
	// str (string): the string to display in the console
	// msgClass (string, optional): to apply a CSS class on the debug message (typically, "info", "error", "success")
	// seriousness (integer, optional): the seriousness of the debug comment. The ones above 1 are currently ignored.
	function trace(str, msgClass, seriousness) {
		
		if (!seriousness) seriousness = 1;
		
		// TODO: make the minimum seriousness configurable
		if (seriousness < debugLevel) return;

		// If the debugZone object is available print the comment in it !
		if ((typeof debugZone) == 'object') {

			// Create a new dom element, and insert it at the top of the zone element
			var newEntry = document.createElement('p');
			newEntry.innerHTML = str;
			if (msgClass)newEntry.className = msgClass;
			debugZone.insertBefore(newEntry, debugZone.firstChild);
		}

		// If there's no loggingZone yet, save all the messages in an array to display later
		// I changed, it will always save them even when the log is displayed.
		// So if the panel is reoladed, will display the whole historic.
		if ('undefined' == typeof traceLog) {
			traceLog = new Array;
		}
		traceLog.push({str:str, msgClass:msgClass});
	}

	// function: Get a value from cookie
	function getCookie(id) {
		return getParam(id, document.cookie, ';');
	}

	function setCookie(id, val, age) {
	// function: Save a value from a cookie
	// age (integer, optional) is the maximum age in day
		if (!age)age = 90;
		var expire = new Date().getTime()+(1000*60*60*24*age); /* Convert age in miliseconds */
		var expDate = new Date(expire);
		document.cookie = id+"="+val+"; expires="+expDate.toGMTString()+"; path=/;";
		if (!document.cookie)return false;
	}

	// Split a string, and return the value of the asked parameter.
	// When only pName is provided, will get a parameter from the URL
	// But can alse be useful to get a parameter's value from cookie, hash, or other random string...
	// Parameters :
	//    pName (string, optional): the parameter's name to get. If not provided, will return an object with all the parameters
	//    pString (string, optional): the string containing all the params, if not given, the current URL querystring will be used
	//    pSeparator (string, optional): the separator charachter, if not given, the "&" will be used
	function getParam(pName, pString, pSeparator) {

		// If there's no string to search in, use the current url, after removing the ' ? ' at the beggining.
		if (typeof pString == 'undefined') pString = window.location.search.substring(1,window.location.search.length);

		// Default separator is '&', for urls
		pSeparator = pSeparator || '&';

		// Initialize the object that will contain all the parameters
		var allParams = {};

		// Split the string with the separator to get separate tokens, and loop trough them
		// To add each parameter and its value to the allParams object
		pList = pString.split(pSeparator);

		for (var i in pList) {
			var pCurrent = pList[i];
			if(typeof pCurrent != 'string') continue;

			var cParam = pCurrent.split('=');
			allParams[(cParam[0].replace(/\s/, ''))] = cParam[1];
		}

		// If an argument has been specified and it is found, return it.
		// Otherwise, return the object containing all the params
		return pName ? allParams[pName] : allParams;
	}

	// Add, replace or remove a parameter in a given string.
	// Parameters :
	//    pName (string): the name of the parameter to add
	//    pValue (string, optional): the value for the param. If not provided and the param is present in the string, it will be removed.
	//    pString (string, optional): the string containing all the params, if not given, the current URL querystring will be used
	//    pSeparator (string, optional): the separator charachter, if not given, the "&" will be used
	function addParam(pName, pValue, pString, pSeparator) {

		var allParams = getParam(null, pString, pSeparator);

		// Add or remove the param
		if (pValue != null) allParams[pName] = pValue;
		else delete allParams[pName];

		// Build the new string with the new param
		var newPString = '';

		for (var i in allParams) {
			var cParam = allParams[i];
			if(typeof cParam == 'object') continue;

			if (cParam) {
				// Separator if more than one item
				newPString += newPString ? pSeparator : '';
				newPString += i + '=' + cParam;
			}
		}
		return newPString;
	}

	// Sometimes the callback functions can't be just a function object (mostly because of the object hierarchy)
	// so, we have to check if it is a string or a function.
	// NOTE: String support disabled, will display an error if a string is given as param! String is evil!
	//    f (function or string): the function that has to be executed
	//    a (anything, optional): the parameter for the function, if needed. Can only be used when f is a function
	function tryCallBack(f, a) {
		if (typeof f == 'function') {
			try {f(a);}
			catch(er) {trace('tryCallBack error'.bold() + ': ' + er + '<hr>' + f + '<hr>', 'error');}
		} else if (typeof f == 'string') alert(f);
	}


	// Test if the given URL is to another domain using DOM
	// Used for Ajax request, to switch to crossdomain support
	function testIfLinkIsInternal(strLink) {

		// Create a ghost DOM link, and assign the URL
		var ghostLink = document.createElement('a');
		ghostLink.href = strLink;

		// This is to help MSIE
		if (!ghostLink.hostname)return true;

		// Return the result of a test between current domain and the link's domain
		return (ghostLink.hostname == self.location.hostname);
	}

	// Will fill the given block with the fillWith. If the block doesn't exist,
	// will create a block with the given ID and append it to the document.
	//    blockId (string): the ID of the HTML element
	//    fillWith (string): the content to add in the element
	function fillBlock(blockId, fillWith) {
		if (!blockId)return false;

		var block = $(blockId);

		// If the block already exists, just fill it with the content
		if (block) {
			block.innerHTML = fillWith;
			block.display = 'block';

		// Otherwise, create a block with the given ID, and fill it
		} else {
			var block = document.createElement('div');
			block.id = blockId;
			document.body.appendChild(block);

			// Fill the block AFTER appending, otherwise javascripts may be executed twice
			block.innerHTML = fillWith;
		}
	}

	// Alias for tsrkit_templateParser
	// This could be done a lot simplier,
	// but i need the object model to be able to add extension to its prototype.
	parseTemplate = function(template, properties, count) {
		var t = new tsrkit_templateParser(template, properties, count);
		return t.template;
	}

	// Will replace the $thing in the template string by the properties.thing value
	// Parameters
	//    template (string): the template, with keywords to search and replace such as $title or $id
	//    properties (object): has to conatin all the data, with keys corresponding to the keywords of template
	//    count (integer, optional): the index in the list, actually the line (1, 2, 3, ...)
	tsrkit_templateParser = function(template, properties, count) {

		if (!template) return;

		if (!properties) {
			var err = '<em>parseTemplate</em>: Sorry, no properties given for template';
			trace(err, 'error');
			return err;
		}

		this.template = template;
		this.properties = properties;
		this.count = count;

		this.additionalParsingOps();

		// Execute it twice, because each keyword may be present more than once.
		// TODO: this could be done better i guess... <-- leo: yes :), see RegExp s/search/replace/g
		for (var i = 0; i < 2; i++) {

			// Special case with the line. Add 1 to the count since most usually the index will start at 0.
			if ('undefined' != typeof this.count) {
				this.count += 1;
				this.template = this.template.replace('$line',((parseInt(this.count) % 2) + 1));
				this.template = this.template.replace('$index',this.count);
			}

			// Replace all the properties
			for (var prop in this.properties) {
				this.template = this.template.replace('$' + prop, this.properties[prop]);
			}
		}
	}


	// Create and return a XMLHttpRequest object, that we'll use for ajax operations
	// copy pasted from http://fettig.net/playground/ajax-subdomain/xmlhttp.js
	// adapted from http://jibbering.com/2002/4/httprequest.html
	tsrkit_createXMLHttp = function() {
		var xmlhttp;
		try {
			xmlhttp = new ActiveXObject("Msxml2.XMLHTTP");
		} catch (e) {
			try {
				xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
			} catch (e) {
				xmlhttp = false;
			}
		}

		if (!xmlhttp && typeof XMLHttpRequest != 'undefined') xmlhttp = new XMLHttpRequest();

		return xmlhttp;
	}


  /**********************/
 /***   EXTENSIONS   ***/
/**********************/

	/*
		Numb methods that may be redefined by user later, to add extensions and new functionalities
		To add an extension, just redefine one of these methods in another file
		Avoid to put site-specific stuff in this file as it's intended to be included as a library
	*/

	// Called when a video is played, and also on player initialisation
	tsrkit.prototype.additionalPlayOps = function() {}


	// Called when a template is parsed
	tsrkit_templateParser.prototype.additionalParsingOps = function() {}

	// Called when some data is loaded with Ahah
	// tsrkit_ahahLoad.prototype.additionalAhahOps = function() {}

	/* Load the file containing extensions */
	loadScript(fileExtension, 'utf-8');

