//////////////////////////////////////////////////////////////////////////////////////////////
//
// track_page.js - User tracking javascript include
//
//////////////////////////////////////////////////////////////////////////////////////////////
//
// usage:
//
// track_me(score)
// track_me(score, data)
// track_me(score, data, UPDATE_HIGHER|UPDATE_ALWAYS|REPLACE_ALL)
// logged_in()
//
// Records are distributed over cookies {user}_HVST0, {user}_HVST1, ..., {user}_HVSTn
// where {user}_COUNT_HARVEST = n;
// Within the cookies " " delimits records and ":" delimits fields.
//
// Version 2: Rewritten by John Alden, November 2000
// Now "smarter" - will only add to a cookie if the path isn't already present
// otherwise it will update the existing record, and repartition the records over the cookies as necessary
// include the companion script debug_scorecard.js for debugging
//
// V2.1: JA - Escapes extra data (if there is any)
// V2.2: JA - Added updating method (allows update regardless of score)
// V2.3: JA - Operates on cookies in unescaped form
// V2.4: JA - Added REPLACE_ALL update method
//
// Version 3: Renamed from scorecard.js by John Alden, Jan 2001
// Deprecated interface discarded; track_me is now NOT called on include
//
// Version 3.1: Cookie lifetime extended to 30 days (John Alden 3/4/2001)
// Version 3.2: (John Alden 22/05/2001)
// - Dates recomputed each time cookie is set (useful when JS file is cached)
// - is_logged_in function added
// - set_cookie now takes an optional expiry time (hours)
// - recharge_login_cookies function added (called from data_to_cookie)
//////////////////////////////////////////////////////////////////////////////////////////////

// Constants
	var UPDATE_HIGHER=0;
	var UPDATE_ALWAYS=1;
	var REPLACE_ALL=2;
	var MAX_ITERATIONS=10000; //to prevent infinite loops if the code is hacked [a bit paranoid, I know...]
	var EXPIRES = 30;         //numbers of days to expiry
	var MAX = 3500;           //max cookie length
	var RD = ' ';
	var FD = ':';

// Globals
	var error_string;

//Silent error handler [can over-ride this for alerting]
function error(val)
{
	error_string=val;
}

//Null debug function to over-ride
function debug(str){}

//Returns values from the current cookie
function get_cookie(name)
{
	var cc = "; " + document.cookie + ";";
	var start = cc.lastIndexOf('; '+name+'=');
	if (start < 0) return "";
	start += name.length + 3;
	end = cc.indexOf(';', start);
	return unescape(cc.substring(start,end));
}

//Expiry is in hours (default is EXPIRES days)
function set_cookie(name,value,expiry)
{
	debug("setting cookie: "+name+" = "+value);
	if(expiry == null) expiry = (EXPIRES*24);
	var internal_date = new Date();
	var internal_now = internal_date.getTime();
	var internal_expires = internal_now + expiry*60*60*1000; //hours to milliseconds
	var expires = new Date(internal_expires);
	var cookie_attribs = '; expires=' + expires.toGMTString() + '; path=/; domain=.bbc.co.uk';
	document.cookie = name+"="+escape(value)+cookie_attribs;
}

function delete_cookie(name)
{
	debug("deleting cookie: "+name);
	var internal_date = new Date();
	var expired_attribs = '; expires=' + internal_date.toGMTString() + '; path=/; domain=.bbc.co.uk';
	document.cookie = name+"="+expired_attribs;
}

function recharge_login_cookies()
{
	set_cookie("USERNAME", get_cookie("USERNAME"), 1);
	set_cookie("VOLATILE_ID", get_cookie("VOLATILE_ID"), 1);
}

//
// Joins all harvest cookie values into a string
// Takes account of the leading and trailing spaces on each cookie
//
function concat_cookies(user)
{
	var last = get_cookie(user + '_COUNT_HARVEST');
	var values = RD;
	for(var i=0; i<=last; i++)
	{
		var record = get_cookie(user + '_HVST' + i);

		//Trim off leading space
		var offset = record.indexOf(RD);
		if(offset != -1)
		{
			offset+=RD.length;
		}
		else
		{
			offset=0;
		}
		values+=record.substring(offset, record.length);
	}
	return values;
}

//
// Splits a string of records across a number of harvest cookies
// optionally starting at cookie "last_cookie"
//
function update_cookies(user, str, last_cookie)
{
	if(last_cookie==null | last_cookie=="") last_cookie=0;
	var curr_cookie_name = user + '_HVST' + last_cookie;
	var curr_cookie_data = RD;
	var was_last = get_cookie(user + '_COUNT_HARVEST');

	//Loop over the data string
	var start = RD.length;
	var end = 0;
	var NOT_INFINITE=MAX_ITERATIONS;
	while(end<str.length && NOT_INFINITE)
	{
		//Next record
		var end = str.indexOf(RD, start);
		if(end==-1) end = str.length;
		record = str.substring(start, end);

      //Assert record is not too long for a cookie of its own
		if(record.length + 4 > MAX)
		{
			error("Record is longer than MAX ("+MAX+")!  Record="+record);
			return 0;
		}

		//Add record to cookies
		if(end<str.length) //don't add empty record at end
		{
			if ((curr_cookie_data.length + record.length + 4) > MAX)
			{
				//cookie full - start filling next one
				if(get_cookie(curr_cookie_name) != curr_cookie_data)
				{
					//rewrite the cookie if it has changed
					set_cookie(curr_cookie_name,curr_cookie_data);
				}
				last_cookie++;
				curr_cookie_name = user + '_HVST' + last_cookie;
				curr_cookie_data = RD + record + RD;
			}
			else
			{
				//fill up the cookie
				curr_cookie_data += record+RD;
			}
		}

		start=end+RD.length;
		NOT_INFINITE--;
	}

	if(!NOT_INFINITE) debug("Infinite loop caught in update_cookies");

	//Write last cookie if changed
	if(get_cookie(curr_cookie_name) != curr_cookie_data)
	{
		set_cookie(curr_cookie_name,curr_cookie_data);
	}

	//Update the number of cookies if changed
	if(get_cookie(user + '_COUNT_HARVEST') != last_cookie)
	{
		set_cookie(user+'_COUNT_HARVEST', last_cookie);
	}

   //Delete any cookies that are finished with
   for(var i = last_cookie+1; i <= was_last; i++) delete_cookie(user + '_HVST' + i);
   //this line added by Cathal Coughlan, 14.06.01 - Netascape 3 requires a value to be returned
   return 0;
}

function update_record(str, url, score, data, update_method)
{
	//Create the new record - make cookie strings backwardly compatible
	//Write ":score:data" if both are defined
	//Write ":score" if there is a score but no data
	//Write "::data" if data but not score is defined
	var new_record = url;
	if(score != null || data != null)
	{
		if(score == null) score="";
		new_record += FD + score;
		if(data != null)  new_record += FD + data;
	}

	//Search though the cookie data for this URL
	var record;
	var start = str.indexOf(url);
	var next_start=0;   //lookahead for next record
	var end=0;          //end of the current record
	var score_start=-1; //position where the score starts
	var max_score=-1;
	var str_without_url = str.substring(0,start);

	var NOT_INFINITE=MAX_ITERATIONS;
	while(start != -1 && next_start != -1 && NOT_INFINITE)
	{
		//Determine record
		end = str.indexOf(RD,start);
		if(end == -1) end=str.length;
		record = str.substring(start,end);

		//Locate score & data
		var last_score=-1;
		var last_str;
		score_start = record.indexOf(FD);
		if(score_start != -1)
		{
			var score_end = record.indexOf(FD, score_start+FD.length);
			if(score_end == -1) score_end = record.length;
			else data_str = record.substring(score_end+FD.length, record.length);
			last_score = parseFloat(record.substring(score_start + FD.length, score_end));
		}
		else score_start=record.length; //used to extract record without score

		//Is score>max?
		if(last_score > max_score) max_score=last_score;

		//Next pass
		var next_start=str.indexOf(url, end+RD.length);
		if(next_start != -1)
		{
		    str_without_url += str.substring(end+RD.length,next_start);
			 start = next_start;
		}
		NOT_INFINITE--;
	}
	if(!NOT_INFINITE) debug("Infinite loop prevented in update_record");
	str_without_url += str.substring(end+RD.length,str.length);

	//Decide how to update the cookie data, based on the update method
	var update = (score > max_score);  //Default to UPDATE_HIGHER
	if(update_method == UPDATE_HIGHER) update=(score > max_score);
	if(update_method == UPDATE_ALWAYS) update=1;

	if(update_method == REPLACE_ALL)
	{
		//Replace all instances
		var instances = MAX_ITERATIONS-NOT_INFINITE;
		debug("replaced "+instances+" instances of "+url+" with "+new_record);
		return str_without_url + new_record + RD;
	}
	else if(update)
	{
		//Replace the last instance of the url
		debug("updating record: old score="+max_score+"; new score = "+score);
		new_str = str.substring(0,start) + new_record;
		if(end<str.length) new_str += str.substring(end,str.length); //this adds a trailing esc_space + the rest of the str
		return new_str;
	}
	else
	{
		//Do nothing
		debug("nothing updated (maybe score is lower)");
		return str;
	}
}

/////////////////////////////////////////////
//
// New API
//
/////////////////////////////////////////////

//Adds/updates the entry in the cookies
function data_to_cookie(user, path, score, data, update_method)
{
	//Check for spaces
	if(data != null)
	{
		data += ""; //JS 1.0 equivalent of String(data)
		if(data.indexOf(" ") != -1)
		{
			error("Extra data must not contain spaces");
			return;
		}
	}

	//Read the cookies into a string of records
	var conc_cookie = concat_cookies(user);
	if(conc_cookie == "") conc_cookie = RD;

	//Attempt to update an existing record if there is one
	var updated = false;
	var update_from=0;
	if ((conc_cookie.length > 0) && (conc_cookie.indexOf(RD+path+RD) != -1  || conc_cookie.indexOf(RD+path+FD) != -1))
	{
		//Already contains this path - update existing record
		debug("Updating existing record: new score="+score+"; new data="+data);
		conc_cookie = update_record(conc_cookie, path, score, data, update_method);
	}
	else
	{
		//Add a new record on the end of the list
		debug("Adding new entry: score="+score+"; data="+data);
		conc_cookie += path;
		if(score != null || data != null)
		{
			if(score == null) score="";
			conc_cookie += FD + score;
			if(data != null) conc_cookie += FD+data;
		}
		conc_cookie += RD;

		//Only need to update the last cookie
		update_from = get_cookie(user + '_COUNT_HARVEST');
	}

	update_cookies(user, conc_cookie, update_from); //Automatically handles splitting if MAX_LENGTH is exceeded
	recharge_login_cookies();
	return;
}

//Tracks a page
function track_me(score, data, update_method)
{
	var user = get_cookie('USERNAME');
	if(user == null || user == "")
	{
		error("unable to determine user");
		return;
	}
	var curr_path = location.pathname + location.search;
	data_to_cookie(user, curr_path, score, data, update_method);
}

//Test whether a user is logged in
function logged_in()
{
	return get_cookie('USERNAME') && get_cookie('VOLATILE_ID');
}

//Make this globally available for backward-compatibility
var username = get_cookie('USERNAME');


