/**
 * Virgin Media Gallery Tool
 * Common JavaScript functions for the public front end
 * @author Tim Whitlock
 * @version $Id: common.js,v 1.4 2009/04/23 18:37:19 tim Exp $
 */


var V_DEBUG = true;
 
 
/**
 * Utility, adds a class name to an element
 */ 
function vElementAddClassName( El, name ){
	// set name if empty
	if( ! El.className ){
		El.className = name;
		return name;
	}
	// else name may be present
	var Reg = new RegExp("\\b"+name+"\\b", "g");
	if( Reg.test( El.className ) ){
		return false;
	}
	// else append name
	El.className += ' ' + name;
	return El.className;
	
}

/**
 * Utility, removes a class name from an element
 */ 
function vElementRemoveClassName( El, name ){
	var Reg = new RegExp("\\b"+name+"\\b", "g");
	El.className = El.className.replace( Reg, '' );
	// @todo strip out extra spaces?
	return El.className;
}
 
 
/**
 * Utility performs simple collection of *scalar* form values into an object
 * @todo this function does not support multiple fields of the same name, and does not support all field types
 */ 
function vCollectFormValues( elForm ){
	var data = {};
	for( var elField, n, i = 0; i < elForm.elements.length; i++ ){
		elField = elForm.elements[i];
		n = elField.name;
		switch( elField.type ){
			case 'checkbox':
				if( elField.checked ){
					data[n] = elField.value;
				}
				break;
			case 'text':
			case 'textarea':
			case 'submit':
			case 'hidden':
				data[n] = elField.value;
		}
	}
	return data;
}
 
 
 


// -- vToggler | for switching on and off interchangable content, e.g. send to friend / report this image 
// -----------
 
function vToggler(){
	this.active = null;
	this.panels = {};
	this.states = {};
}

vToggler.prototype.addToggle = function( linkId, panelId ){
	var elLnk = document.getElementById(linkId);
	var elDiv = document.getElementById(panelId);
	this.panels[linkId] = elDiv;
	this.hide( elDiv );
	var Toggler = this;
	elLnk.onclick = function(){
		Toggler.toggle( this );
		return true; // <- follow #
	}
}

vToggler.prototype.toggle = function( elLnk ){
	try {
		var elDiv = this.panels[elLnk.id];
		if( this.states[elDiv.id] ){
			this.hide( elDiv );
		}
		else {
			if( this.active ){
				this.hide( this.active );
			}
			this.active = elDiv;
			this.show( elDiv );
		}
	}
	catch( Er ){
		V_DEBUG && alert( Er.message );
	}
}

vToggler.prototype.show = function( elDiv ){
	elDiv.style.display = 'block';
	this.states[ elDiv.id ] = true;
}

vToggler.prototype.hide = function( elDiv ){
	elDiv.style.display = 'none';
	this.states[ elDiv.id ] = false;
}

/**
 * @static
 */
vToggler.togglers = {};
vToggler.init = function ( groupId, linkId, panelId ){
	try {
		var Toggler;
		if( vToggler.togglers[groupId] ){
			Toggler = vToggler.togglers[groupId];
		}
		else {
			Toggler = vToggler.togglers[groupId] = new vToggler;
		}
		Toggler.addToggle( linkId, panelId );
		return Toggler;
	}
	catch( Er ){
		V_DEBUG && alert( Er.message );
	}
}






// -- vTabber | For switching between tabbed areas
// ---------- 

var vTabber = function( groupId ){
	this.groupId = groupId
	this.activeTab = null;
	this.activeBody = null;
	this.tabs = {};
}

vTabber.prototype.setCookieValue = function( tabId ){
	// ensure path is under gallery
	var apath = location.pathname.split('/');
	document.cookie = this.groupId+'='+tabId+'; path=/'+apath[1];
}

vTabber.prototype.getCookieValue = function(){
	// pull id out named by tabber group
	var Reg = new RegExp("\\b"+this.groupId+"=(\\w+);", "g");
	var r = Reg.exec( document.cookie );
	return r ? r[1] : '';
}

vTabber.prototype.addTab = function( tabId, bodyId ){
	var elTab = document.getElementById( tabId );
	if( ! elTab ){
		//throw new Error('No tab "'+tabId+'"'); // <- debug only, tab is allowed to not exist.
		return;
	}
	var elDiv = document.getElementById( bodyId );
	if( ! elDiv ){
		throw new Error('Tabber body not found '+bodyId);
	}
	this.tabs[ tabId ] = elDiv;
	// enable currently enabled tab if set in cookie
	var activeId = this.getCookieValue();
	if( activeId === tabId ){
		// activate default
		this.showBody( elDiv );
		this.disableTab( elTab );
	}
	else if( activeId ){
		// wait for default.. this is not it
		this.hideBody( elDiv );
		this.enableTab( elTab );
	}
	else if( this.activeBody ) {
		// there is not default, and this is not the first
		this.hideBody( elDiv );
		this.enableTab( elTab );
	}
	else {
		// this is the first
		this.showBody( elDiv );
		this.disableTab( elTab );
	}

	return this;
}


vTabber.prototype.toggle = function( tabId ){
	try {
		// hide existing open tab
		if( this.activeBody ){
			this.hideBody( this.activeBody );
			this.enableTab( this.activeTab );
		}
		var elDiv = this.tabs[ tabId ];
		var elTab = document.getElementById( tabId );
		// show new tab body
		this.showBody( elDiv );
		this.disableTab( elTab );
		// set as current
		this.setCookieValue( tabId );
	}
	catch( Er ){
		V_DEBUG && alert( Er.message );
	}
}

vTabber.prototype.showBody = function( elDiv ){
	elDiv.style.display = 'block';
	this.activeBody = elDiv;
}

vTabber.prototype.hideBody = function( elDiv ){
	elDiv.style.display = 'none';
}

// disabling tab means making it the active one; i.e. highlighted and unclickable
vTabber.prototype.disableTab = function( elTab ){
	vElementAddClassName( elTab, 'active' );
	this.activeTab = elTab;
	var elLink = elTab.getElementsByTagName('a')[0];
	if( ! elLink ){
		throw new Error('No link found inside tab');
	}
	var Tabber = this;
	elLink.onclick = function(){
		return false;
	}
	//elLink.style.display.cursor = 'default'; // <- @todo in CSS?
}
// enabing a tab means making it clickable and dulled out
vTabber.prototype.enableTab = function( elTab ){
	vElementRemoveClassName( elTab, 'active' );
	var elLink = elTab.getElementsByTagName('a')[0];
	if( ! elLink ){
		throw new Error('No link found inside tab');
	}
	var Tabber = this;
	elLink.onclick = function(){
		Tabber.toggle( elTab.id );
		return false; // <- prevent #
	}
	//elLink.style.display.cursor = 'pointer'; // <- @todo in CSS ?
}

/**
 * @static
 */
vTabber.tabbers = {};
vTabber.init = function ( groupId, tabId, bodyId ){
	try {
		var Tabber;
		if( vTabber.tabbers[groupId] ){
			Tabber = vTabber.tabbers[groupId];
		}
		else {
			Tabber = vTabber.tabbers[groupId] = new vTabber( groupId );
		}
		Tabber.addTab( tabId, bodyId );
		return Tabber;
	}
	catch( Er ){
		V_DEBUG && alert( Er.message );
	}
 	
 }



/**
 * Very simple AJAX function. posts a form and dumps the response back into specified DIV as innerHTML
 * @param HTMLFormElement
 * @param Object
 * @param string
 * @return bool whether successful 
 */
function vInitSimpleAjaxRequest( elForm, data, responseId ){
	try {
		var Req = vGetHttpRequest();
		if( ! Req ){
			return false;
		}
		Req.onreadystatechange = function(){
			try {
				// "this !== Req" in IE6 
				if( Req.readyState == 4 ){
					var elDiv = document.getElementById(responseId);
					elDiv.innerHTML = Req.responseText;
				}
			}
			catch( Er ){
				//alert( Er.message );
			}
		}
		Req.open( elForm.method, elForm.action, true );
		Req.setRequestHeader('Content-Type','application/x-www-form-urlencoded');	
		data.ajax = '1';
		Req.send( vEncodePostData(data) );
		return true;
	}
	catch( Er ){
		// alert( Er.message );
	}
	return false;
}



/**
 * Simple post data encoder
 */
function vEncodePostData( data ){
	var pairs = [];
	for( var s in data ){
		pairs.push( encodeURIComponent(s) +'='+encodeURIComponent(data[s]) );
	}
	return pairs.join('&');
}



/**
 * Get a handle on a cross-browser XMLHTTPRequest object
 */
function vGetHttpRequest(){
	var Req = null;
	try { // to get the standards-compliant XMLHttpRequest Object
        Req = new XMLHttpRequest();
		if( Req == null ){ throw 'no XMLHttpRequest'; } 
    }
	catch( e ){
        try { // to get MS HTTP request Object
            Req = new ActiveXObject("Msxml2.XMLHTTP.4.0");
			if( Req == null ){ throw 'no Msxml2.XMLHTTP.4.0'; } 
        }
		catch( e ){
            try { // to get MS HTTP request Object
                Req = new ActiveXObject("Msxml2.XMLHTTP");
				if( Req == null ){ throw 'no Msxml2.XMLHTTP'; } 
            }
			catch( e ){
                try { // to get the old MS HTTP request Object
                    Req = new ActiveXObject("microsoft.XMLHTTP"); 
					if( Req == null ){ throw 'no microsoft.XMLHTTP'; } 
                }
				catch( e ){
					// ignore
                }
            }    
        }
    }
    return Req;
}



/**
 * Test an email address is valid.
 */	
function vEmailValid( s ){
	var bits = s.split('@');
	if( bits.length !== 2 ){
		return false;
	}
	var user = bits[0];
	var host = bits[1];
	// validate local part
	// fail if single illegal character found.
	if( /[^\!#\$%&'\*\+-\/\=\?\^_`\{\|\}~a-zA-Z0-9\.]/.test(user) ){ //' 
		return false;
	} 
	// "." not allowed as first or last character
	if( user.substr(0,1) === '.' || user.substr(user.length-1,1)  === '.' ){
		return false;
	}
	// validate domain part
	var names = host.split('.');
	for( var n = 0; n < names.length; n++ ){
		switch(  names[n].substr(0,1) ){
		case '-':
			// may not start with a hyphen
			return false;
		}
		if(  names[n].length < 2 ){
			// minimum length of 2 characters
			return false;
		}
	}
	if( /[^-\.a-z0-9]/i.test(host) ){
		// bad character found
		return false;
	}
	// all tests passed
	return true;
}




/**
 * Post photo report form to ajax service
 * @return bool false to prevent form posting
 */
function vReportPhoto( elForm, responseId ){
	try {
		var data = {
			report:  '1',
			comment: elForm.comment.value
		};
		return ! vInitSimpleAjaxRequest( elForm, data, responseId );
	}
	catch( Er ){
		V_DEBUG && alert( Er.message );
		return true;
	}
}



/**
 * post photo comment to ajax service
 */
function vCommentPhoto( elForm, responseId ){
	try {
		var data = {
			"comment[name]": elForm["comment[name]"].value,
			"comment[region]": elForm["comment[region]"].value,
			"comment[comment]": elForm["comment[comment]"].value
		};
		if( ! data["comment[name]"] ){
			alert('Please enter your name');
			return false;
		}
		if( ! data["comment[region]"] ){
			alert('Please enter your location');
			return false;
		}
		if( ! data["comment[comment]"] ){
			alert('Please enter a comment');
			return false;
		}
		return ! vInitSimpleAjaxRequest( elForm, data, responseId );
	}
	catch( Er ){
		V_DEBUG && alert( Er.message );
		return true;
	}
}



/**
 * post send-to-friend form to ajax service
 * @return bool false to prevent form posting
 */
function vSendPhoto( elForm, responseId ){
	try {
		var empty = [];
		var fields = {
			sender_name: "Your name",
			sender_email: "Your email",
			rcpt_name: "Your friend's name",
			rcpt_email: "Your friend's email address"
		}
		var data = {
			sendmail: '1',
			comment:  elForm.comment.value 
		};
		for( var f in fields ){
			if( ! elForm[f].value ){
				empty.push( fields[f] );
			}
			else {
				data[f] = elForm[f].value;
			}
		}
		if( empty.length ){
			alert("Please complete the required fields:\n - " + empty.join("\n - ") );
			return false;
		}
		if( ! vEmailValid(data.sender_email) ){
			alert("Your email address is invalid");
			return false;
		}
		if( ! vEmailValid(data.rcpt_email) ){
			alert("Your friend's email address is invalid");
			return false;
		}
		return ! vInitSimpleAjaxRequest( elForm, data, responseId );
	}
	catch( Er ){
		V_DEBUG && alert( Er.message );
		return true;
	}
}



/**
 * Validate photo upload/entry form
 */
function validateEntryForm( elForm, skipFile ){
	try {
		if( ! skipFile && ! elForm['f'].value ){
			throw new Error('Please choose a photo to upload');
		}
		if( elForm['user[name]'] && ! elForm['user[name]'].value ){
			throw new Error('Please enter your name');
		}
		if( elForm['user[email]'] ){
			var email = elForm['user[email]'].value;
			if( ! email || ! vEmailValid(email) ){
				throw new Error('Please enter a valid email address');
			}
			if( email !== elForm['email_confirm'].value ){
				throw new Error('Please check your email confirmation');
			}
		}
		if( elForm['photo[title]'] && ! elForm['photo[title]'].value ){
			throw new Error('Please enter a title for your photo');
		}
		if( elForm['acceptterms'] && ! elForm['acceptterms'].checked ){
			throw new Error('Please read and accept the terms and conditions');
		}
		// all passed
		return true;
	}
	catch( Er ){
		alert( Er.message );
		return false;
	}
}



/**
 * Flash-ize the uploader form
 * @param string id of the form element 
 * @param string name of the file field
 * @return bool
 */
function initFlashUploader( formId, fieldName, swfpath, width, height ){
	try {
		var elForm  = document.getElementById( formId );
		var elField = elForm[fieldName];
		if( ! elField.id ){
			elField.id = '_'+formId+'_'+fieldName;
		}
		// Perform Flash-replacement of field element via SWFObject
		var minvers = '9.0.0';
		if( ! swfobject.hasFlashPlayerVersion(minvers) ){
			// AS3 required... abort replacement
			return false;
		}
		// ok to replace with swf in skins folder passed as param
		var flashvars  = {};
		var params     = {
			menu: "false",
			scale: "noScale",
			allowScriptAccess: "always",
			wmode: "transparent"
		};
		var attributes = {};
		// use MAX_FILE_SIZE hidden field to ascertain max file size
		if( elForm['MAX_FILE_SIZE'] ){
			flashvars['V_PHOTO_MAX_BYTES'] = elForm['MAX_FILE_SIZE'].value;
		}
		elField.style.display = 'none';
		swfobject.embedSWF( swfpath, elField.id, String(width), String(height), minvers, 'expressInstall.swf', flashvars, params, attributes );
		// Alter form submission to use alternative onsubmitter
		var objId = elField.id;
		elForm.onsubmit = function(){
			vSubmitFlashPhotoUploader( this, objId, fieldName );
			return false;
		}
	}
	catch( Er ){
		V_DEBUG && alert( Er.message );
	}
	return true;
}


/**
 * Proxy onsubmit function for submitting via flash uploader
 */
function vSubmitFlashPhotoUploader( elForm, objId, fieldName ){
	var swfObj = document.getElementById( objId );
	try {
		if( swfObj.jsBusy() ){
			throw new Error('Still processing.\nPlease wait.');
		}
		if( ! swfObj.jsFileChosen() ){
			throw new Error('Please choose a photo to upload');
		}
		if( ! validateEntryForm( elForm, true ) ){
			return false;
		}
		// post form via flash movies
		var data = vCollectFormValues( elForm );
		data.isFlash = '1';
		if( ! swfObj.jsPostData( elForm.action, data, fieldName ) ){
			throw new Error('Failed to post data via the Flash uploader\nTry disabling javascript and refreshing the page');
		}
	}
	catch( Er ){
		alert( Er.message );
	}
	return false;
}



/**
 * 5-star rating object
 * Maintains the rollover state of UI
 * @param String id of accessible form element
 * @param String if of list containing rating links
 * @param Number average rating to display
 */
function vFiveStarRatingObject( formId, listId, average ){
	this.average = 0;
	this.enabled = false;
	this.items = [];
	this.links = [];
	this.states = [];
	this.elForm = document.getElementById(formId);
	try {
		// Hide accessible form, this holds the post URL
		this.elForm.style.display = 'none';
		// Grab and initialize appropriate symbols (1-5)
		var elList = document.getElementById(listId);
		var elItem, elItems = elList.getElementsByTagName('li');
		for( var i = 0; i < elItems.length; i++ ){
			elItem = elItems[i];
			if( elItem.className.indexOf('rateSymbol') !== -1 ){
				var elLink = elItem.getElementsByTagName('a')[0];
				this.items.push( elItem );
				this.links.push( elLink );
				this.initLink( elLink, i );
			}
		}
		// set initial default state
		this.restoreAverage( average );
		// add a mouseout handler on the entire list
		var Me = this;
		elList.onmouseout = function(){
			Me.restoreAverage( Me.average );
		}
		// done
		this.enabled = true;
	}
	catch( Er ){
		V_DEBUG && alert( Er.message );
		return true;
	}
}

/**
 * Mouse event handler (click)
 */
vFiveStarRatingObject.prototype.onSymbolClick = function( i ){
	if( ! this.enabled ){
		alert('You have already rated this photo');
		return;
	}
	var rating = i + 1;
	// Disable and show loading message
	this.elForm.innerHTML = '<h2>rating photo ...</h2>';
	this.elForm.style.display = 'block';
	this.restoreAverage( rating );
	this.enabled = false;
	var data = {
		rating: String( rating )
	};
	vInitSimpleAjaxRequest( this.elForm, data, this.elForm.id );
}

/**
 * Mouse event handler (over)
 */
vFiveStarRatingObject.prototype.onSymbolOver = function( i ){
	if( this.enabled ){
		for( var j = 0; j <= i; j++ ){
			this.displayItemOver( j );
		}
	}
}

/**
 * Add dirty mouse handlers - no inline attributes allowed in HTML
 */
vFiveStarRatingObject.prototype.initLink = function( elLink, i ){
	var Me = this;
	elLink.onclick = function(){
		Me.onSymbolClick( i );
		return false;
	}
	elLink.onmouseover = function(){
		Me.onSymbolOver( i );
		return false;
	}
}

/**
 * Restores the state of the UI to a given average
 */
vFiveStarRatingObject.prototype.restoreAverage = function ( average ){
	this.average = average;
	for( var elItem, i = 0; i < this.items.length; i++ ){
		// Dull out star symbol, unless average is higher than item index
		if( i < average ){
			this.displayItemOn(i);
			this.states[i] = 1;
		}
		else {
			this.displayItemOff(i);
			this.states[i] = 0;
		}
	}
}

/**
 * Utility - highlights item with permanent "on" state
 */
vFiveStarRatingObject.prototype.displayItemOn = function ( i ){
	var elItem = this.items[i];
	vElementRemoveClassName(elItem, 'rateSymbolInactive');
	vElementAddClassName(elItem, 'rateSymbol');
}

/**
 * Utility - highlights item with temporary "over" state
 */
vFiveStarRatingObject.prototype.displayItemOver = function ( i ){
	var elItem = this.items[i];
	vElementRemoveClassName(elItem, 'rateSymbol');
	vElementRemoveClassName(elItem, 'rateSymbolInactive');
	vElementAddClassName(elItem, 'rateSymbolOver');
}

/**
 * Utility - dulls item to "off"/"inactive" state
 */
vFiveStarRatingObject.prototype.displayItemOff = function ( i ){
	var elItem = this.items[i];
	vElementRemoveClassName(elItem, 'rateSymbol');
	vElementAddClassName(elItem, 'rateSymbolInactive');
}


/**
 * Boolean - up/down rating UI
 */
function vBooleanRatingObject( formId, listId, value ){
	this.enabled  = false;
	this.downLink = null;
	this.upLink   = null;
	this.elForm = document.getElementById(formId);
	try {
		// Hide accessible form, this holds the post URL
		this.elForm.style.display = 'none';
		// Grab and initialize appropriate symbols (-1/1)
		var elList = document.getElementById(listId);
		var elItem, elItems = elList.getElementsByTagName('li');
		for( var i = 0; i < elItems.length; i++ ){
			elItem = elItems[i];
			if( elItem.className.indexOf('booleanRate') !== -1 ){
				var elLink = elItem.getElementsByTagName('a')[0];
				if( ! this.downLink ){
					this.downLink = elLink;
					this.initLink( elItem, elLink, -1 );
				}
				else if( ! this.upLink ){
					this.upLink = elLink;
					this.initLink( elItem, elLink, 1 );
				}
			}
		}
		// done
		this.enabled = true;
	}
	catch( Er ){
		V_DEBUG && alert( Er.message );
		return true;
	}
}


/**
 * 
 */
vBooleanRatingObject.prototype.initLink = function( elItem, elLink, value ){
	var Me = this;
	var classname = elItem.className;
	var overclass = classname + 'Over';
	elLink.onclick = function(){
		Me.onSymbolClick( value );
		return false;
	};
	elLink.onmouseover = function(){
		if( Me.enabled ){
			vElementRemoveClassName( elItem, classname );
			vElementAddClassName( elItem, overclass );
		}
	};
	elLink.onmouseout = function(){
		if( Me.enabled ){
			vElementRemoveClassName( elItem, overclass );
			vElementAddClassName( elItem, classname );
		}
	};
}


/**
 * Mouse event handler (click)
 */
vBooleanRatingObject.prototype.onSymbolClick = function( value ){
	if( ! this.enabled ){
		alert('You have already rated this photo');
		return;
	}
	// Disable and show loading message
	this.elForm.innerHTML = '<h2>rating photo ...</h2>';
	this.elForm.style.display = 'block';
	this.enabled = false;
	var data = {
		rating: String( value )
	};
	vInitSimpleAjaxRequest( this.elForm, data, this.elForm.id );
}


/**
 *
 */
function vVotingObject( formId, butName, pid ){
	try {
		var elForm = document.getElementById( formId );
		this.elBut = elForm[butName];
		var Me = this; 
		elForm.onsubmit = function(){
			return Me.onFormSubmit( this, pid );
		}
	}
	catch( Er ){
		V_DEBUG && alert( Er.message );
		return true;
	}
}


vVotingObject.prototype.onFormSubmit = function( elForm, pid ){
	try {
		var currentVote = this.getCookieValue();
		if( currentVote == pid ){
			alert('You have already voted for this picture');
			return false;
		}
		else if( currentVote && ! confirm('This will replace your previous vote!') ){
			return false;
		}
		else {
			this.setCookieValue( pid );
			// Disable and show loading message
			this.elBut.value = 'Wait';
			vInitSimpleAjaxRequest( elForm, {}, elForm.id );
			return false;
		}
	}
	catch( Er ){
		V_DEBUG && alert( Er.message );
		return true;
	}
}


vVotingObject.prototype.setCookieValue = function( pid ){
	// ensure path is under gallery
	var apath = location.pathname.split('/');
	document.cookie = 'vpid='+pid+'; path=/'+apath[1];
}

vVotingObject.prototype.getCookieValue = function(){
	// pull id out named by tabber group
	var Reg = new RegExp("\\bvpid=(\\d+);", "g");
	var r = Reg.exec( document.cookie );
	return r ? r[1] : '';
}




/**
 * Initialize accessible comment reporting form/link
 * @return void
 */
function vInitCommentReporter( formId, linkId, contId ){
	try {
		var elForm = document.getElementById( formId );
		var elLink = document.getElementById( linkId );
		elForm.style.display = 'none';
		elForm.onsubmit = function(){
			vInitSimpleAjaxRequest( this, {}, contId );
			return false;
		}
		elLink.onclick = function(){
			elForm.style.display = 'block';
			return false;
		}
	}
	catch( Er ){
		V_DEBUG && alert( Er.message );
	} 
}










