/**
 * @fileoverview
 * Provides most of the base functionality for ODAT Community.
 */

/* Setup namespaces */
if (typeof(BCNTRY) == 'undefined') { BCNTRY = {}; }

/**
 * Holds data and functions relating to community on ODAT.
 */
BCNTRY.odatComm = {

	/**
	* Sets several elements of the ODAT index page based
	* on cookie values. Some of these cookie values are retrieved indirectly using the
	* user object (BCNTRY.odatComm.currentUser). Specifically, it will set the following:
	*
	* TODO: This is a terrible function name
	*
	*		Element							Cookie Value
	*
	*		#displayName					review_display_name
	*		#display_name					review_display_name
	*		#dealTalkWelcomeLogged[In/Out]	hidden/shown based on user.isLoggedIn
	*		#dealTalkWelcomeDisplayName		user.reviewDisplayName
	*/
	updateDOM: function() {
		BCNTRY.showName('displayName');
		BCNTRY.showName('display_name');

		//update deal talk welcome message
		var user = BCNTRY.odatComm.currentUser;
		yd.setStyle('dealTalkWelcomeLoggedIn', 'display', user.isLoggedIn ? 'block' : 'none');
		yd.setStyle('dealTalkWelcomeLoggedOut', 'display', !user.isLoggedIn ? 'block' : 'none');
		$('dealTalkWelcomeDisplayName').innerHTML = user.reviewDisplayName;
	},

	/**
	 * uploadReviewSubmitOnClick is an event handler intended to trigger the submit of
	 * an instant review when the submit button is clicked.
	 * @returns {Boolean} Boolean value indicating success
	 */
	uploadReviewSubmitOnClick:function() {
		yd.addClass(document.body, 'yui-busy');
		try {
			var f = BCNTRY.odatComm.InstantReviewForm.current;
			f.submit();
		} catch(err) {
			if(window.console){console.log(err);}
		}
		yd.removeClass(document.body, 'yui-busy');
		return true;
	},

	/**
	 * Sets the thumbs up/down portion of the instant review
	 * form. This function will set the hidden value in the form as well as update
	 * the display of the UI elements.
	 * @param {Boolean} isUp Whether up should be selected (or false for down)
	 */
	setReviewThumbsUpDown: function(isUp) {
		yd.addClass('reviewThumbs' + (isUp ? 'Up' : 'Down'), 'active');
		yd.removeClass('reviewThumbs' + (isUp ? 'Up' : 'Down'), 'deactive');

		yd.removeClass('reviewThumbs' + (isUp ? 'Down' : 'Up'), 'active');
		yd.addClass('reviewThumbs' + (isUp ? 'Down' : 'Up'), 'deactive');

		$('review_form').isThumbsUp.value = isUp ? '1' : '0';
		$('people_agree').innerHTML = $('review_total'+(isUp ? 'Up' : 'Down')).innerHTML + ' others agree';
		yd.removeClass('people_agree', (isUp ? 'Down' : 'Up'));
		yd.addClass('people_agree', (isUp ? 'Up' : 'Down'));

		//Hide the help text
		BCNTRY.odatComm.hideHelpText($('isThumbsUp'), true);
		yd.setStyle('people_agree', 'display', 'block');
		fade_in('people_agree');
	},

	/**
	 * Clears the thumbs up/down portion of the instant review form, so
	 * that neither option is selected.
	 */
	clearReviewThumbsUpDown: function() {
		yd.removeClass('reviewThumbsUp', 'active');
		yd.removeClass('reviewThumbsUp', 'deactive');

		yd.removeClass('reviewThumbsDown', 'active');
		yd.removeClass('reviewThumbsDown', 'deactive');
		$('review_form').isThumbsUp.value = '';
		$('people_agree').innerHTML = '';
		yd.setStyle('people_agree', 'display', 'none');
	},

	/**
	 * Creates a new instance of
	 * BCNTRY.odatComm.InstantReviewForm and binds it to the form on page.
	 * @returns {InstantReviewForm} The instance of InstantReviewForm that was created
	 */
	initInstantReviewForm:function() {
		var ir_form = $('review_form');

		//The second element of each array should be an element ID. The click
		//event for these elements will be set up to call changeLoginState(state)
		//and will pass the first element of the array as the state to change to.
		var stateMap = [
				[BCNTRY.odatComm.InstantReviewForm.RETURNING, 'returning_link'],
				[BCNTRY.odatComm.InstantReviewForm.NEW, 'new_link'],
				[BCNTRY.odatComm.InstantReviewForm.NEW,'new_link2'],
				[BCNTRY.odatComm.InstantReviewForm.RETURNING,'returning_link2']
		];
		var f = new BCNTRY.odatComm.InstantReviewForm(ir_form);
		f.bindStateChangeEls(stateMap);

		f.onStateChange = function(new_status) {
			if( new_status== BCNTRY.odatComm.InstantReviewForm.FORGOT_PASSWORD ) {
				yd.addClass('mv_username','forgot_input');
			}
			else {
				yd.removeClass('mv_username','forgot_input');
			}
		};

		//clear thumbs up/down
		BCNTRY.odatComm.clearReviewThumbsUpDown();

		//called to sync state
		var status = BCNTRY.odatComm.currentUser.isLoggedIn ? BCNTRY.odatComm.InstantReviewForm.LOGGED_IN : f.status;
		f.changeLoginState(status, true);

		return f;
	},

	/**
	 * messages is an object containing various messages that are to be used in
	 * error and success scenarios.
	 */
	messages: {
		review: {
			flag: {
				SUCCESS: 'Good Catch! Thanks for giving everyone the heads up.',
				ERR_UNKNOWN: 'There was an error flagging the review.',
				ERR_NO_FLAGS: 'Why\'d you flag this?',
				ERR_TOO_MANY: 'You can only flag 2 items without logging in.'
			}
		}
	},

	/**
	 * A convenience function that makes its way up the element
	 * heirarchy until it reaches the 'parent' element of the given classname.
	 */
	getParentElement: function(el, classname) {
		while( (el = el.parentNode) ) {
			if (el.className === classname) {
				return el;
			}
		}
	},

	/**
	 * Shows a full reveiw by id. It is
	 * intended to be an event handler, but it does not accept an event object as
	 * a parameter. Therefore, it can be called in any scenario.
	 * @param {Int} review_id
	 * @param {Boolean} isThumbsUp
	 * @param {Boolean} showSpacer
	 */
	show_full_review_onclick: function(review_id, isThumbsUp, showSpacer) {
		var r = new BCNTRY.instantReview({review_id: review_id});
		var container1;
		var container2;

		if (isThumbsUp === true) {
			container1 = $('bucketUp');
			container2 = $('bucketDown');
		}
		else {
			container1 = $('bucketDown');
			container2 = $('bucketUp');
		}

		//Calculate randomly the position of the bubble in the reviews wall
		var containerSpace = yd.getStyle(container1, 'width');
		containerSpace = containerSpace.substring(0, containerSpace.length-2);
		containerSpace = (containerSpace - 340);
		var reviewPosition = Math.round(Math.random() * containerSpace);

		//Create the node to be added to the review to the wall
		var new_review = document.createElement("div");
		new_review.id = "ir_id_" + review_id;
		new_review.className = "review";
		new_review.style.marginLeft = reviewPosition + "px";
		new_review.innerHTML = r.asHTML();
		container1.insertBefore(new_review, container1.firstChild);

		//r.load(function() {
		//	container.innerHTML = r.asHTML();
		//});

		//Create the spacer node to be added to the opposite side of the wall
		if (showSpacer === true) {
			var spacer = document.createElement("div");
			spacer.className = "review_spacer";
			spacer.style.height = yd.getStyle('ir_id_' + review_id, 'height');
			container2.insertBefore(spacer, container2.firstChild);
		}
	},

	/**
	 * Intended to be an event handler that triggers the submit
	 * of the "flag review" form (via AJAX). It does not accept an event object as a parameter so it can
	 * be called in non-even scenarios.
	 * @param {HTMLElement} flag_form The flag form to be submitted
	 */
	flag_review_submit_onclick: function(review_id) {
		var r = new BCNTRY.instantReview();
		r.initById(review_id);
		r.flag();
	},

	/**
     * Triggers voting a review as helpful or unhelpful.
	 * @param {Int} reviewId The id of the review to be voted on
	 * @param {Boolean} isHelpful Indicates if the review was helpful or not
	 */
    voteOnClick: function(reviewId, isHelpful) {
        if (BCNTRY.odatComm.voteClicked) {
            return false;
        }
        BCNTRY.odatComm.voteClicked = true;
        reviewId = reviewId.toString();
        var r = new BCNTRY.instantReview();
        r.initById(reviewId);
        r.castVote ({
            success: function(o) {
                BCNTRY.odatComm.voteClicked = false;
                BCNTRY.odatComm.instantReviewManager.updateInstantReviewData(o.responseText);
                BCNTRY.odatComm.instantReviewDomManager.redrawInstantReview(reviewId);

				//omniture tracking
				BCNTRY.odat.scBuffer.handleHelpfulVote(isHelpful);
            },
            error: function(o) {
                BCNTRY.odatComm.voteClicked = false;
            },
            failure: function(o) {
                BCNTRY.odatComm.voteClicked = false;
            }
        }, isHelpful);
    }
};

/**
 * Represents the instant review form on
 * the ODAT index page. The constructor binds to the form on page and sets the initial
 * status (hiding fields which don't need to be displayed).
 * @constructor
 * @param {HTMLFormElement} srcForm The instant review form to be bound to
 *
 * @class
 * Here is some stuff that is for teh class, noot the consturcto
 */
BCNTRY.odatComm.InstantReviewForm = function(instantReviewForm) {
	/**
	 * The HTML Form Element to bind to.
	 */
	this.srcForm = instantReviewForm;

	/**
	 * The current status (also called state in other places for some reason) of the form.
	 * the possible values for this are stored in {@link BCNTRY.odatComm.InstantReviewForm#VALID_STATES}.
	 */
	this.status = BCNTRY.odatComm.InstantReviewForm.RETURNING; //the default state, unless logged in

	this.srcForm.status.value = this.status; //sync value in form

	var oThis = this;

	BCNTRY.odatComm.InstantReviewForm.map(
		this.srcForm.getElementsByTagName('input'),
		function(el) { 
		  ye.addListener(el, 'blur', function() {
                        BCNTRY.odatComm.hideHelpText(el);
                        oThis.validate();
                  }, oThis, true);
		}
	);

	BCNTRY.odatComm.InstantReviewForm.map(
		this.srcForm.getElementsByTagName('textarea'),
		function(el) { 
		  ye.addListener(el, 'blur', function() {
			BCNTRY.odatComm.hideHelpText(el);
			oThis.validate();
		  }, oThis, true)
		}
	);

	// Thumbs Up/Down
	ye.addListener('reviewThumbsUp', 'click', function() { 
		BCNTRY.odatComm.hideHelpText('isThumbsUp');
		oThis.inputToBeValidated["VOTE"] = true;
		if(oThis.status === "ir_logged_in") { oThis.validate(); }
	});
	ye.addListener('reviewThumbsDown', 'click', function() {
		BCNTRY.odatComm.hideHelpText('isThumbsUp');
		oThis.inputToBeValidated["VOTE"] = true;
		if(oThis.status === "ir_logged_in") { oThis.validate(); }
	});

	// Review comment
	ye.addListener("reviewComment", "focus", function() {
		BCNTRY.odatComm.clearDefaultField(this);
		BCNTRY.odatComm.showHelpText(this, oThis.inputToBeValidated["REVIEWCOMMENT"]);
		oThis.inputToBeValidated["VOTE"] = true;
		oThis.validate();
	});
	ye.addListener("reviewComment", "blur", function() {
		BCNTRY.odatComm.restoreDefaultField(this);
		oThis.inputToBeValidated["REVIEWCOMMENT"] = true;
	});
	ye.addListener("reviewComment", "keydown", function (e) { 
		BCNTRY.odatComm.textCounter(this, 'charCounter', 140); 
		oThis.inputToBeValidated["REVIEWCOMMENT"] = true;
	});
	ye.addListener("reviewComment", "keyup", function (e) { 
		BCNTRY.odatComm.textCounter(this, 'charCounter', 140); 
		if((oThis.status === "ir_logged_in") && (this.value.length <= 1)) { 
			BCNTRY.odatComm.hideHelpText('reviewComment'); 
			oThis.validate(); 
		};
	}); 

	// Username
	ye.addListener("mv_username", "focus", function() { 
		oThis.inputToBeValidated["VOTE"] = true;
		oThis.inputToBeValidated["REVIEWCOMMENT"] = true;
		if (oThis.status == "new") {
			oThis.inputToBeValidated["FIRSTNAME"] = true;
			oThis.inputToBeValidated["LASTNAME"] = true;
			oThis.inputToBeValidated["SCREENNAME"] = true;
		}
		oThis.validate();
		oThis.inputToBeValidated["MV_USERNAME"] = true; 
		if(oThis.status === "new" ){
			BCNTRY.odatComm.showHelpText(this, oThis.inputToBeValidated["MV_PASSWORD"]);
		}
		else {
			//Highlight the field
			var theParent = this.parentNode || this.parent;
			yd.addClass(theParent, 'active');
		};
	});

	// Password
	ye.addListener("mv_password", "focus", function() { 
		oThis.inputToBeValidated["VOTE"] = true;
		oThis.inputToBeValidated["REVIEWCOMMENT"] = true;
		if (oThis.status == "new") {
			oThis.inputToBeValidated["FIRSTNAME"] = true;
			oThis.inputToBeValidated["LASTNAME"] = true;
			oThis.inputToBeValidated["SCREENNAME"] = true;
			BCNTRY.odatComm.showHelpText(this,oThis.inputToBeValidated["MV_PASSWORD"]);
		} 
		else {
			//Highlight the field
			var theParent = this.parentNode || this.parent;
			yd.addClass(theParent, 'active');
		};
		oThis.inputToBeValidated["MV_USERNAME"] = true;
		oThis.validate();
		oThis.inputToBeValidated["MV_PASSWORD"] = true; 
	});
	ye.addListener("mv_password", "keyup", function (e) {
		if (this.value.length >= 4) { oThis.validate(); }
	});

	// First name
	ye.addListener("firstName", "focus", function() { 
		BCNTRY.odatComm.showHelpText(this,oThis.inputToBeValidated["FIRSTNAME"]);
		oThis.inputToBeValidated["VOTE"] = true;
		oThis.inputToBeValidated["REVIEWCOMMENT"] = true;
		oThis.validate();
		oThis.inputToBeValidated["FIRSTNAME"] = true; 
	});

	// Last name
	ye.addListener("lastName", "focus", function() { 
		BCNTRY.odatComm.showHelpText(this,oThis.inputToBeValidated["LASTNAME"]);
		oThis.inputToBeValidated["VOTE"] = true;
		oThis.inputToBeValidated["REVIEWCOMMENT"] = true;
		oThis.validate();
		oThis.inputToBeValidated["LASTNAME"] = true; 
	});

	// Screen name
	ye.addListener("screenName", "focus", function() { 
	 	BCNTRY.odatComm.showHelpText(this,oThis.inputToBeValidated["SCREENNAME"]);	
		oThis.inputToBeValidated["VOTE"] = true;
		oThis.inputToBeValidated["REVIEWCOMMENT"] = true;
		oThis.inputToBeValidated["FIRSTNAME"] = true;
		oThis.inputToBeValidated["LASTNAME"] = true;
		oThis.validate();
		oThis.inputToBeValidated["SCREENNAME"] = true; 
	});

	//show/hide the real name fields when the real name radio is toggled
	ye.addListener('realNameYes', 'click', this.hideRealNameFields, this, true);
	ye.addListener('realNameNo', 'click', this.showRealNameFields, this, true);

	//update screen name when name fields are changed
	ye.addListener('firstName', 'keyup', this.updateScreenName, this, true);
	ye.addListener('lastName', 'keyup', this.updateScreenName, this, true);

	//show or hide real name hint
	ye.addListener('screenName', 'focus', this.showOrHideRealNameHint, this, true);
	ye.addListener('screenName', 'keyup', this.showOrHideRealNameHint, this, true);
	ye.addListener('screenName', 'blur', this.hideRealNameHint, this, true);

	//load status, force update
	this.changeLoginState(this.status,true);
	return this;
};

/**
 * Generates a validator based on a supplied predicate.
 * @param A function that returns true if the value is valid, or false if not
 * @returns {Function} A validator function based on the supplied predicate
 */
BCNTRY.odatComm.InstantReviewForm.generateValidator = function(predicate) {
	return function(arr_els) {
		var errors = 0;
		var shadow = this;
		BCNTRY.odatComm.InstantReviewForm.map(arr_els, function(arr_el) { 
			if( !predicate(shadow.srcForm[arr_el.el].value) ) {
				errors |= BCNTRY.odatComm.InstantReviewForm.error[arr_el.err];
			}
		});
		return errors;
	};
};

/**
 *	An object (used as a map) that stores the valid form states.
 */
BCNTRY.odatComm.InstantReviewForm.VALID_STATES = [ 
	BCNTRY.odatComm.InstantReviewForm.NEW='new',
	BCNTRY.odatComm.InstantReviewForm.RETURNING='returning',
	BCNTRY.odatComm.InstantReviewForm.FORGOT_PASSWORD='forgot_pass',
	BCNTRY.odatComm.InstantReviewForm.LOGGED_IN='ir_logged_in' //prefix with _ir because css class "logged_in" is used for something else
];

/**
 * Determins if a given status is valid by checking
 * if it exists in the VALID_STATES object.
 * @param {String} status
 * @returns {Boolean} A boolean indicating if the status is valid
 */
BCNTRY.odatComm.InstantReviewForm.valid_status = function(status) {
	/* static method/predicate of the User class for determining valid login states */
	return BCNTRY.odatComm.InstantReviewForm.indexOf(BCNTRY.odatComm.InstantReviewForm.VALID_STATES,status) != -1 ;
};

/**
 * Determins if a given element has a class that is the same
 * as one of the values contained in VALID_STATES. This is used to show or hide elements based
 * on the form state. If an element has a one of the classes which map to a state, but not the
 * current state, it is hidden. If it has the class that matches the current state, is it made
 * visible. If it has none of the classes, it is exempt from this prcess.
 * @param {HTMLElement} the HTML elememt to  be tested
 * @returns {Boolean} A boolean indicating if the element has one of the classes
 */
BCNTRY.odatComm.InstantReviewForm.elHasLoginClass = function(el) {
	var res = BCNTRY.odatComm.InstantReviewForm.reduce(
		BCNTRY.odatComm.InstantReviewForm.VALID_STATES,
		function(acc,i) { return acc || yd.hasClass(el,i);},
		false
	);
	return res;
};

/**
 * Returns the current selected value of a radio button group.
 * @param {Array[HTMLElement]} els An array containing the radio button elements
 * @returns {String} The current value of the radio button group
 */
BCNTRY.odatComm.InstantReviewForm.getRadioValue = function(els) {
    for(var i=0; i<els.length; i++) {
		if(els[i].checked) {
            return els[i].value;
        }
	}
};

/**
 * Selects a radio button by it's value, within a radio button
 * group.
 * @param {Array[HTMLElement]} els An array of radio button elements
 * @param {String} value The value to set the button group to
 */
BCNTRY.odatComm.InstantReviewForm.setRadioValue = function(els, value) {
    for(var i=0; i<els.length; i++) {
		els[i].checked = (els[i].value == value);
	}
};

/**
 * Executes the supplied function on each element of the supplied array.
 * @param {Array} arr The target array
 * @param {Function} fn The function to execute on each element
 * @param {Object} opt An arbitrary object to pass to [fn] each time it is called
 */
BCNTRY.odatComm.InstantReviewForm.map = function(arr,fn,opt) {
	for(var i=0;i<arr.length;i++) {
		fn(arr[i],opt);
	}
};

/**
 * Returns the index of a given value in an array. If the element is not
 * found in the array, returns -1. This method is implemented in most
 * major browsers, but is missing from IE. That is why it is implemented
 * here.
 * @param {Array} arr The array to be searched
 * @param {Object} obj The object to be searched for
 * @returnss {Integer} The index where the element was found, or -1 if it was not found
 */
BCNTRY.odatComm.InstantReviewForm.indexOf = function(arr,obj){//because ie chooses not to implement Array.indexOf
	for(var i=0; i<arr.length; i++){
		if(arr[i]==obj){
			return i;
		}
	}
	return -1;
};

/**
 * Executes a given function on each element of an array, each time
 * storing the results in [acc]. Acc is also passed into the function
 * each time, giving the callback function the opportunity to modify
 * it in each iteration in any way.
 * @param {Array} arr The array to be iterated on
 * @param {Function} fn The function to call on each array element
 * @param {Object} acc The object to be modified by each function call
 * @returns The final value of acc after all iterations
 */
BCNTRY.odatComm.InstantReviewForm.reduce = function(arr,fn,acc) {
	for(var i=0;i<arr.length;i++) {
		acc = fn(acc,arr[i]);
	}
	return acc;
};

BCNTRY.odatComm.InstantReviewForm.prototype = {
	/**
	 * Tracks if the form is currently being submitted, in order to prevent it
	 * from being subitted twice at any given time. The form can be submitted
	 * more than once, but one submit must complete before a subsequent submit
	 * can be triggered.
	 */
	form_submitted: false,

	inputToBeValidated: {
	   VOTE: false,
	   REVIEWCOMMENT: false, 
	   FIRSTNAME: false,
	   LASTNAME: false,
	   SCREENNAME: false,
	   MV_USERNAME: false,
	   MV_PASSWORD: false
	},
	
	/**
	 * Hides fields that allow an existing user to specify a new display name.
	 */
	hideRealNameFields: function() {yd.setStyle('realNameFields', 'display', 'none');},

	/**
	 * Shows fields that allow an existing user to specify a new display name.
	 */
	showRealNameFields: function() {yd.setStyle('realNameFields', 'display', 'block');},

	/**
	 * Hides fields that allow the user to specify if their current display name
	 * is real and input a new display name if not.
	 */
	hideRealNameArea: function() {yd.setStyle('realName', 'display', 'none');},

	/**
	 * Shows fields that allow the user to specify if their current display name
	 * is real and input a new display name if not.
	 */
	showRealNameArea: function() {yd.setStyle('realName', 'display', 'block');},

	/**
	 * Generates our best guess at the user's real name by concatenating the
	 * user's first and last name fields. By default, firstName and lastName
	 * are the fields used, but you can supply different fields to use.
	 * @param {HTMLElement} firstNameField The field where the first name should be retrieved from
	 * @param {HTMLElement} lastNameField The field where the last name should be retrieved from
	 * @returns {String} The screen name (which we assume is a *real* name)
	 */
	generateScreenName: function(firstNameField, lastNameField) {
		if(!firstNameField) {
			firstNameField = this.srcForm.firstName;
		}
		if(!lastNameField) {
			lastNameField = this.srcForm.lastName;
		}
		var firstName = firstNameField.value == firstNameField.defaultValue ? '' : firstNameField.value;
		var lastName = lastNameField.value == lastNameField.defaultValue ? '' : lastNameField.value;
		var screenName = firstName;
		if(lastName.length > 0) {
			if(screenName.length > 0) {
				screenName += ' ';
			}
			screenName += lastName;
		}
		return screenName;
	},

	/**
	 * Sets which forgot password link/message should show in the instant
	 * review form. There are two possible messages. 1) The message before
	 * the password has ben reset and 2) The message after the password has
	 * been reset.
	 * @param {Boolean} isAfter If true, use the second (after) message, else the before message
	 */
	setForgotPassMsg: function(isAfter) {
		var els = document.getElementById('forgot_link').getElementsByTagName('a');
		YAHOO.util.Dom.setStyle(els[0], 'display', isAfter ? 'none' : 'inherit');
		YAHOO.util.Dom.setStyle(els[1], 'display', isAfter ? 'inherit' : 'none');
	},

	/**
	 * Determins whether the screen name the user typed in is "real" by checking
	 * if it is equal to the output of {@link #generateScreenName}.
	 * @returns {Boolean} Whether the user-supplied screen name is real
	 * @see #generateScreenName
	 */
	isRealName: function() {
		return (this.srcForm.screenName.value == this.generateScreenName());
	},

	/**
	 * Sets the value of the screen name input to the value generated by
	 * {@link $generateScreenName}.
	 * @returns {String} The value that the screen name field was set to
	 * @see #generateScreenName
	 */
	updateScreenName: function() {
		return (this.srcForm.screenName.value = this.generateScreenName());
	},

	/**
	 * Shows or hides the real name hint (a message that explains and
	 * encourages the use of real name). Whether the hint is shown
	 * or hidden depends on the output of #isRealName. If false, then
	 * it shows, else it hides.
	 * @see #isRealName
	 * @see #hideRealNameHint
	 * @see #showRealNameHint
	 */
	showOrHideRealNameHint: function() {
		return (this.isRealName() ? this.hideRealNameHint() : this.showRealNameHint());
	},

	/**
	 * Shows the real name hint (a message that explains and encourages
	 * use of real name).
	 */
	showRealNameHint: function() {
		if(!this.isRealNameHintVisible) {
			fade_in('realNameHint');
			this.isRealNameHintVisible = true;
		}
	},

	/**
	 * Hides the real name hint (a message that explains and encourages
	 * use of real name).
	 */
	hideRealNameHint: function() {
		if(this.isRealNameHintVisible) {
			fade_out('realNameHint');
			this.isRealNameHintVisible = false;
		}
	},

	/**
	 * Resets the form to it's initial state so that it can be filled out
	 * and submitted again. This is intended to be used after successful
	 * form submission via AJAX.
	 */
	clearForm: function() {
		// save isRealName because we want to override what srcForm.reset() will do
		var isRealName = BCNTRY.odatComm.InstantReviewForm.getRadioValue(this.srcForm.isRealName);
		// call reset on the form
		this.srcForm.reset();
		// restore value of isRealName
		BCNTRY.odatComm.InstantReviewForm.setRadioValue(this.srcForm.isRealName, isRealName);
		// grey out reviewComment, firstName, lastName, realFirstName, realLastName
		BCNTRY.odatComm.restoreDefaultField('reviewComment');
		BCNTRY.odatComm.restoreDefaultField('firstName');
		BCNTRY.odatComm.restoreDefaultField('lastName');
		BCNTRY.odatComm.restoreDefaultField('realFirstName');
		BCNTRY.odatComm.restoreDefaultField('realLastName');
		// clear out thumbs up/down icons (remove active class from both)
		BCNTRY.odatComm.clearReviewThumbsUpDown();
		$('charCounter').innerHTML = "140";
	},

	/**
	 * Submits the instant review form. If the user is logged out, then the
	 * form will be submitted "normally" (by posting to a new page within
	 * the current window). If the user is logged in, the submission will be
	 * via AJAX. The AJAX submission is handled by {@link BCNTRY.odatComm.InstantReview}.
	 */
	submit: function() {
		var shadow = this;
		var user = BCNTRY.odatComm.currentUser;

		// only allow form to be submitted once
		if( this.form_submitted ) { return false; }
		if( this.validate() !== 0) { throw 'login form validation failed'; }

		this.form_submitted = true;

		// update real name data. if they're already using real name, we shouldn't change it
		if (user.isLoggedIn && !user.useRealName) {
			var isRealName = BCNTRY.odatComm.InstantReviewForm.getRadioValue(
				this.srcForm.isRealName) == 'yes' ? true : false;
			var realName = this.generateScreenName(this.srcForm.realFirstName, this.srcForm.realLastName);
			if (isRealName) {
				user.useRealName = true;
			}
			else {
				if (realName) {
					user.useRealName = true;
					user.reviewDisplayName = realName;
				}
				else {
					user.useRealName = false;
				}
			}
		}

		//create instant review object and init from form
		var instantReview = new BCNTRY.instantReview();
		instantReview.initByForm(this.srcForm);
		if (user.isLoggedIn) {
			instantReview.submit({
				success: function(o) {
					shadow.form_submitted = false;
					BCNTRY.odatComm.updateDOM();
					shadow.changeLoginState(shadow.status, true);
					shadow.clearForm();
					BCNTRY.odat.scBuffer.trackInstantReviewFormSubmitted(
						instantReview.obj.isThumbsUp,
						instantReview.obj.user.useRealName);
				},
				failure: function() {
					shadow.form_submitted = false;
				},
				error: function(o) {
					shadow.form_submitted = false;
				}
			});
		}
		else {
			//omniture tracking
			_setCookie('sc_buffer', escape(BCNTRY.odat.scBuffer.serialize()));
			BCNTRY.odat.scBuffer.cfg.sendOnUnload = false;

			this.srcForm.serializedInstantReview.value = instantReview.asJSON();
			this.srcForm.submit();
		}
	},

	/**
	 * Checks the cookie data for a serialized version of the instant reveiw
	 * formand uses it to populate the current form. The use case here is when
	 * the user fills out the form, submits it in a logged out state (which
	 * causes a page load) and has an error in the form. The data to prepopulate
	 * the form with what they already typed in must be passed via cookie because
	 * the ODAT index page is loaded from a CDN. If there were any errors with
	 * the previous submission, they will be in the cookie too and this function
	 * will render them as well.
	 * @param {String} cookieName The name of the cookie to look for form data in
	 */
	initFromCookie: function(cookieName) {
		var instant_review = null;
		var cookieData = _readCookie(cookieName);
		if (!cookieData) {
			return;
		}
		_deleteCookie(cookieName);
		instant_review = YAHOO.lang.JSON.parse(cookieData);

		//set form state
		BCNTRY.odatComm.InstantReviewForm.current.changeLoginState(instant_review.status);

		//prepopulate form fields
		BCNTRY.odatComm.setReviewThumbsUpDown(
			instant_review.isThumbsUp ? true : false
		);
		if (instant_review.reviewComment) {
			this.srcForm.reviewComment.value = instant_review.reviewComment;
		}
		if (instant_review.firstName) {
			this.srcForm.firstName.value = instant_review.firstName;
		}
		if (instant_review.lastName) {
			this.srcForm.lastName.value = instant_review.lastName;
		}
		if (instant_review.screenName) {
			this.srcForm.screenName.value = instant_review.screenName;
		}
		if (instant_review.mv_username) {
			this.srcForm.mv_username.value = instant_review.mv_username;
		}

		//render errors
		if (instant_review.errors) {
			this.renderErrors(instant_review);
			document.location.hash = 'dealTalk';
		}
	},

	/**
	 * Changes the state of the instant review form. See {@link #VALID_STATES} for
	 * a list of valid form states. Note: status and state are used interchangeably
	 * here for some reason. We should probably change that.
	 * @param {String} new_status The new form status
	 * @param {Boolean} force If false, the form will not be updated if the current status is the same as the new status
	 */
	changeLoginState:function( new_status , force ) {
		if(!BCNTRY.odatComm.InstantReviewForm.valid_status(new_status)) { throw "Invalid Login Status"; }
		if(this.status == new_status && !force) { return; }
		var i = 0;

		this.clearErrors();
		BCNTRY.odatComm.showHelpText($('isThumbsUp'));

		for (i in this.inputToBeValidated) {
			this.inputToBeValidated[i] = false;
		};

		yd.removeClass('submit_vote', 'active');
		$('submit_vote').disabled = true;

		//fire state change event
		try {
			if(this.onStateChange) {
				this.onStateChange.call(this, arguments);
			}
		} 
		catch (err) {
			if(window.console) { console.log(err); }
		}

		//update form elements
		this.srcForm.status.value = new_status;

		//new requirement, allow arbitrary els on the page to be registered
		var candidate_els = this.srcForm.getElementsByTagName('*');
		if(this.__stateful_els) {
			var tmp=[];
			for(i=0;i<candidate_els.length;i++) { tmp.push(candidate_els[i]); }
			candidate_els=tmp.concat(this.__stateful_els);
		}
		for(i=0;i<candidate_els.length;i++) {
			if( !BCNTRY.odatComm.InstantReviewForm.elHasLoginClass( candidate_els[ i ] ) ) { continue; }
			var display=yd.hasClass(candidate_els[ i ],new_status)?'':'none';
			yd.setStyle(candidate_els[ i ],'display',display);
		}

		//custom logic for real name area
		var user = BCNTRY.odatComm.currentUser;
		if(user.isLoggedIn) {
			if(user.useRealName) {
				this.hideRealNameArea();
			} 
			else {
				this.showRealNameArea();
				if(BCNTRY.odatComm.InstantReviewForm.getRadioValue(this.srcForm.isRealName) == 'yes')
				  this.hideRealNameFields();
			}
		}

		//update object to new status
		this.status = new_status;
	},

	/**
	 * Takes a map of elements to form states and adds a click handler function
	 * that changes the form state (hiding/showing elements) to the state the
	 * element maps to.
	 * @param {Object} elMap An object (used as a map) of elements that should be clicked to change mapped to the state they represent
	 * @see #bindStateChangeEl
	 */
	bindStateChangeEls: function(elMap) {
		BCNTRY.odatComm.InstantReviewForm.map(
			elMap,
			this.bindStateChangeEl(this)
		);
	},

	/**
	 * Creates a function that adds a click handler for a state changing element.
	 * @param {BCNTRY.odatComm.InstantReviewForm} scope The current InstantReviewForm instance
	 * @param {Function} The function that will add the listener
	 */
	bindStateChangeEl: function(scope) {
		return function(el) { 
			if(BCNTRY.odatComm.InstantReviewForm.indexOf(BCNTRY.odatComm.InstantReviewForm.VALID_STATES,el[0]) == -1) { throw "Invalid Login Status:" + el[0]; }
			ye.addListener(el[1],'click',function(e) { ye.preventDefault(e);scope.changeLoginState(el[0]); });
		};
	},

	/**
	 * Renders errors as found in the the login_response object (this name should be
	 * changed). The errors should be found in login_response.errors and they should
	 * be a single integer. The errors are stored as an integer using a binary mask.
	 * The mask for each error is stored in {@link #error}.
	 * @param {Object} login_response An object that contains an attribute called "errors", which is what we really want
	 */
	renderErrors: function(login_response) {
		BCNTRY.errors = login_response.errors;
        	var i;

		// do nothing else if there are no errors to render
		if(!login_response.errors) { return; }

		var error_message='';
		var field_message = document.createElement("span");
		var allErrors = BCNTRY.odatComm.InstantReviewForm.error;

		for(i in BCNTRY.odatComm.InstantReviewForm.error) { 
			var error = BCNTRY.odatComm.InstantReviewForm.error[i];
			var errorMessage = BCNTRY.odatComm.InstantReviewForm.errorMessages[i] || i;
			if(login_response.errors & error) {
				if( error == allErrors.NO_PASSWORD
					|| error == allErrors.NO_FIRST_NAME
					|| error == allErrors.NO_LAST_NAME
					|| error == allErrors.NO_REALNAME
					|| error == allErrors.NO_EMAIL
					|| error == allErrors.NO_VOTE
					|| error == allErrors.NO_REVIEW_BODY 
					|| error == allErrors.INVALID_EMAIL
					|| error == allErrors.INVALID_PASS
					|| error == allErrors.EMAIL_EXISTS
					|| error == allErrors.EMAIL_DNE
					) {
					if(window.console){console.log(errorMessage);}

					this.signifyError(this.errorElementList[i]);

					if(this.errorElementList[i] == "mv_password") {
						BCNTRY.odatComm.hideHelpText($('mv_password'));
					};

					field_message = document.createElement("span");
					field_message.className = "error_message";
					field_message.innerHTML = errorMessage;

					BCNTRY.odatComm.InstantReviewForm.map(this.errorElementList[i],
					  function(el) {
						BCNTRY.odatComm.hideHelpText($('isThumbsUp'));
						$(el).parentNode.appendChild(field_message); 
					});

				}
				else if( error == allErrors.INCORRECT_EMAIL_OR_PASS) {
					this.signifyError(this.errorElementList[i]);

					field_message = document.createElement("span");
					field_message.innerHTML = errorMessage;
					field_message.className = "error_message error_shared";
					BCNTRY.odatComm.InstantReviewForm.map(this.errorElementList[i],function(el) {
						$(el).parentNode.appendChild(field_message);
					});
				}
				else {
					if(window.console){console.log('Unhandled error: ' + errorMessage);}
				}
			}
		}
	},

	/**
	 * A map of what elements each error {@see #errors} should be rendered by.
	 */
	errorElementList: {
		NO_VOTE:['isThumbsUp'],
		NO_REVIEW_BODY:['reviewComment'],

		NO_EMAIL:['mv_username'],
		NO_PASSWORD:['mv_password'],
		NO_USERNAME:['mv_username'],

		NO_FIRST_NAME:['firstName'],
		NO_LAST_NAME:['lastName'],
		NO_REALNAME:['screenName'],

		INVALID_EMAIL:['mv_username'],
		INVALID_PASS:['mv_password'],
		INCORRECT_EMAIL_OR_PASS:['mv_username','mv_password'],
		EMAIL_EXISTS:['mv_username'],
		EMAIL_DNE:['mv_username']
	},


	/**
	 * Visually updates the UI for each supplied element (which should be a field)
	 * to show that the element has an error. It does this by adding the CSS class
	 * 'login_error' to the parent node of each of these elements.
	 * @param {Array} els An array of elements (form fields) that have errors
	 */
	signifyError: function(els) {
		if(!els) { return; }
		//given html elements, signify errors
		BCNTRY.odatComm.InstantReviewForm.map(els,function(el) {
			yd.addClass($(el).parentNode,'login_error');
		});
	},

	/**
	 * Clears all form errors so there are no visisible error messages.
	 */
	clearErrors:function() {
		for(i in this.errorElementList) {
			BCNTRY.odatComm.InstantReviewForm.map(this.errorElementList[i],function(el) {
				yd.removeClass($(el).parentNode,'login_error');
				yd.getElementsByClassName('error_message', 'span', el.parentNode, function(el){el.parentNode.removeChild(el)});
			});
		};
	},

	/**
	 * A validator that checks for a non-empty value.
	 * @param {Array} arr_els An array of form fields whose values should be validated
	 * @returns {Integer} An integer representing a set of binary error flags
	 * @see #generateValidator
	 * @see #errors
	 */
	validateNonEmpty:BCNTRY.odatComm.InstantReviewForm.generateValidator(function(el) {return !/^\s*$/.test(el);}),

	/**
	 * A validator that checks for a valid password. Note that this only
	 * checks for correct format, not for a correct password matching a
	 * username. This validator could pass and the password could still be
	 * incorrect. The password must be 4 characters or more in length and
	 * must not contain any of the following characters:^!-~
	 * @param {Array} arr_els An array of form fields whose values should be validated
	 * @returns {Integer} An integer representing a set of binary error flags
	 * @see #generateValidator
	 * @see #errors
	 */
	validatePass:BCNTRY.odatComm.InstantReviewForm.generateValidator(function(el) {return el.length>=4;}),

	/**
	 * A validator that checks for a valid email address.
	 * @param {Array} arr_els An array of form fields whose values should be validated
	 * @returns {Integer} An integer representing a set of binary error flags
	 * @see #generateValidator
	 * @see #errors
	 */
	validateEmail:BCNTRY.odatComm.InstantReviewForm.generateValidator(function(el) { return (/^[^@]+@[^@]+\.[^@]+$/).test(el); }),

	/**
	 * A validator that checks for a valid Boolean value represented as an integer. In
	 * other words, the value must be a "1" or a "0".
	 * @param {Array} arr_els An array of form fields whose values should be validated
	 * @returns {Integer} An integer representing a set of binary error flags
	 * @see #generateValidator
	 * @see #errors
	 */
	validateBool:BCNTRY.odatComm.InstantReviewForm.generateValidator(function(el) {return (/^[10]$/).test(el);}),

	/**
	 * A validator that makes sure the supplied fields' values are not equal to their
	 * default values. The default values checked against are stored in the "defaultValue"
	 * attribute of the supplied elements.
	 * @param {Array} arr_els An array of form fields whose values should be validated
	 * @returns {Integer} An integer representing a set of binary error flags
	 * @see #generateValidator
	 * @see #errors
	 */
	validateNonDefault:function(arr_els) {
		var errors = 0;
		var shadow = this;
		BCNTRY.odatComm.InstantReviewForm.map(arr_els, function(arr_el) { 
			if( shadow.srcForm[arr_el.el].value == shadow.srcForm[arr_el.el].defaultValue ) {
				errors |= BCNTRY.odatComm.InstantReviewForm.error[arr_el.err];
			}
		});
		return errors;
	},
	
	/**
	 * Validates the instant review form using the validator functions of this object.
	 * The fields which are validated is determined by the form state. If there are
	 * errors, they will be automatically rendered using {@link #renderErrors}.
	 * @returns {Boolean} True if the form was valid, false otherwise
	 * @see #VALID_STATES
	 * @see #renderErrors
	 */
	validate: function() {
		//clear errors
		this.clearErrors();
		//check for required fields
		var errorObj = { errors:0 };
		var readyForSubmit = true;

		if (this.inputToBeValidated["VOTE"] == true)  {
			errorObj.errors|=this.validateBool([{el:'isThumbsUp',err:'NO_VOTE'}]);
		};

		if (this.inputToBeValidated["REVIEWCOMMENT"] == true)  {
			errorObj.errors|=this.validateNonDefault([ {el:'reviewComment',err:'NO_REVIEW_BODY'} ]);
			errorObj.errors|=this.validateNonEmpty([ {el:'reviewComment',err:'NO_REVIEW_BODY'} ]);
		};

		readyForSubmit = readyForSubmit & this.inputToBeValidated["VOTE"] & this.inputToBeValidated["REVIEWCOMMENT"];

		switch(this.status) {
		    case BCNTRY.odatComm.InstantReviewForm.RETURNING:
			if (this.inputToBeValidated["MV_USERNAME"] == true)  {
				errorObj.errors|=this.validateNonEmpty([
					{el:'mv_username',err:'NO_EMAIL'}
				]);
				
				errorObj.errors&BCNTRY.odatComm.InstantReviewForm.error.NO_EMAIL || (
				errorObj.errors|=this.validateEmail([
					{el:'mv_username',err:'INCORRECT_EMAIL_OR_PASS'}
				]));
			};
			if (this.inputToBeValidated["MV_PASSWORD"] == true)  {
				errorObj.errors|=this.validateNonEmpty([
					{el:'mv_password',err:'NO_PASSWORD'}
				]);
				errorObj.errors&BCNTRY.odatComm.InstantReviewForm.error.NO_PASSWORD || (
					errorObj.errors|=this.validatePass([
					{el:'mv_password',err:'INVALID_PASS'}
				]));
			};
			readyForSubmit = readyForSubmit & this.inputToBeValidated["MV_USERNAME"] & this.inputToBeValidated["MV_PASSWORD"];
			break;

		    case BCNTRY.odatComm.InstantReviewForm.NEW:
			if (this.inputToBeValidated["FIRSTNAME"] == true)  {
				errorObj.errors|=this.validateNonEmpty([ {el:'firstName',err:'NO_FIRST_NAME'} ]); 
				errorObj.errors|=this.validateNonDefault([ {el:'firstName',err:'NO_FIRST_NAME'} ]);
			};

			if (this.inputToBeValidated["LASTNAME"] == true)  {
				errorObj.errors|=this.validateNonEmpty([ {el:'lastName',err:'NO_LAST_NAME'} ]); 
				errorObj.errors|=this.validateNonDefault([ {el:'lastName',err:'NO_LAST_NAME'} ]);
			};
			
			if (this.inputToBeValidated["SCREENNAME"] == true)  {
				errorObj.errors|=this.validateNonEmpty([ {el:'screenName',err:'NO_REALNAME'} ]); 
				//check for length of realname, 32 chars
				if(this.srcForm.screenName.value.length>32) {
					this.srcForm.screenName.value = this.srcForm.screenName.value.substr(0,32);
				};
			};

			if (this.inputToBeValidated["MV_USERNAME"] == true) {
				errorObj.errors|=this.validateNonEmpty([ {el:'mv_username',err:'NO_EMAIL'} ]); 
				//check for valid email if not empty
				errorObj.errors&BCNTRY.odatComm.InstantReviewForm.error.NO_EMAIL || (
					errorObj.errors|=this.validateEmail( [ {el:'mv_username',err:'INVALID_EMAIL'} ] )
				);
			};

			if (this.inputToBeValidated["MV_PASSWORD"] == true)  {
				errorObj.errors|=this.validateNonEmpty([
					{el:'mv_password',err:'NO_PASSWORD'}
				]);
				errorObj.errors&BCNTRY.odatComm.InstantReviewForm.error.NO_PASSWORD || (
					errorObj.errors|=this.validatePass([
					{el:'mv_password',err:'INVALID_PASS'}
				]));
			};

			readyForSubmit = readyForSubmit & this.inputToBeValidated["FIRSTNAME"] & this.inputToBeValidated["LASTNAME"] & this.inputToBeValidated["SCREENNAME"] & this.inputToBeValidated["MV_USERNAME"] & this.inputToBeValidated["MV_PASSWORD"];
			break;

		    case BCNTRY.odatComm.InstantReviewForm.FORGOT_PASSWORD:
			errorObj.errors|=this.validateNonEmpty( [ {el:'mv_username',err:'NO_EMAIL'} ] );
			errorObj.errors&BCNTRY.odatComm.InstantReviewForm.error.NO_EMAIL || (
				errorObj.errors|=this.validateEmail( [ {el:'mv_username',err:'INVALID_EMAIL'} ] )
			);
			break;
		};

		this.renderErrors(errorObj);
		// enable/disable the submit button
		if((errorObj.errors == 0) && (readyForSubmit == true)){
			yd.addClass('submit_vote', 'active');
			$('submit_vote').disabled = false;
		} else {
			yd.removeClass('submit_vote', 'active');
			$('submit_vote').disabled = true;
		};

		return errorObj.errors?1:0;
	}
};//end User.prototype

/**
 * A map of possible form errors to their respective
 * mask which can be used to check for the error in the
 * error value.
 *
 * Example Usage:
 *   var errors = 4; //This would probably come from a server response
 *   //check for the NO_PASSWORD error
 *   if(errors & this.error.NO_PASSWORD) {
 *     //do something about the error
 *   }
 */
BCNTRY.odatComm.InstantReviewForm.error = {
	NO_PASSWORD:1<<0,
	NO_EMAIL:1<<1,
	NO_REALNAME:1<<2,
	INVALID_EMAIL:1<<3,
	INVALID_PASS: 1<<4,
	INCORRECT_EMAIL_OR_PASS: 1<<5,
	ALREADY_LOGGED_IN: 1<<6,
	EMAIL_EXISTS: 1<<7,
	UNKNOWN: 1<<8,
	TOO_MANY:1<<9,
	EMAIL_DNE:1<<10,
	NO_VOTE: 1<<11,
	NO_REVIEW_BODY: 1<<12,
	NO_FIRST_NAME: 1<<13,
	NO_LAST_NAME: 1<<14
};

/**
 * A map of possible errors the error message to display for that error.
 * @see #error
 */
BCNTRY.odatComm.InstantReviewForm.errorMessages = {
	NO_EMAIL:'Please enter your email address.',
	NO_PASSWORD:'<em>Please enter a password</em> Must be at least 4 characters.',
	// Invalid email message when creating new account
	INVALID_EMAIL:"Invalid email address. Please try again.",
	INVALID_EMAIL_OR_PASS:'Incorrect email or password. Check your CAPS lock Steepandcheap.com passwords are case sensitive.',
	INVALID_PASS:"<em>Invalid password.</em> Must be at least 4 characters.",
	NO_REALNAME:'Please enter your screen name.',
	NO_FIRST_NAME:'Please enter your first name.',
	NO_LAST_NAME:'Please enter your last name.',
	// Invalid email or password when loggin in
	INCORRECT_EMAIL_OR_PASS:"You have entered an invalid password or username.",
	TOO_MANY:"You've unsuccessfully tried to reset your password too many times. This function will lock out for some minutes.",
	EMAIL_EXISTS:"Sorry. It looks like the address submitted is already in use in our systems. Please try a different account.",
	EMAIL_DNE:"Whoops Your username isn't in our systems. Please re-enter your correct username.",
	NO_VOTE:"<em>Whoops.</em> Looks like you forgot to choose thumbs up or thumbs down.",
	NO_REVIEW_BODY:"<em>Whoops.</em> You didn't type a review comment."
};

/**
 * Triggers a asynchronous XML HTTP Request. It uses YAHOO's implementation
 * and does a few extra things that we do every time like setting request
 * headers and checking the response for a success attribute. If the args parameter
 * is an Object, it will be converted toa string using {@link #serializeQueryParams}.
 * @param {String} method The HTTP method (HEAD, GET, POST, PUT, DELETE)
 * @param {String} url The HTTP resource URL
 * @param {Object} callback A map of callback methods (success, failure, error)
 * @param {String|Object} args Arguments to pass as the request body (or in the URL for a GET request)
 */
BCNTRY.odatComm.asyncRequest = function(method, url, callback, args) {
	if(typeof(args) === 'object') { args = this.serializeQueryParams(args); }
	yc.initHeader("accept-charset", "UTF-8");
    yc.setDefaultPostHeader(false);
	yc.initHeader("Content-Type", "application/json; charset=utf-8");
    yc.asyncRequest(method, url, {
        success: function(o) {
            if (typeof callback !== 'undefined' &&
				typeof callback.success === 'function') {
                callback.success(o);
            }
        },
        failure: function(o) {
            if (typeof callback !== 'undefined' &&
                typeof callback.failure === 'function') {
                callback.failure(o);
            }
        },
        error: function(o) {
            if (typeof callback !== 'undefined' &&
                typeof callback.error === 'function') {
                callback.error(o);
            }
        }
    }, args);
	yc.resetDefaultHeaders();
};

/**
 * Takes an Object and turns it into a string that can be submitted
 * as the body of an HTTP request or in the URL of a GET request.
 * @param {Object} p The params to be serialized
 * @returns {String} Serialized params suitable to be used in an HTTP request body
 */
BCNTRY.odatComm.serializeQueryParams = function(p) {
	var params = [];
	for (var i in p) {
        if (typeof(i) !== 'function') {
            params.push( i + "=" + encodeURIComponent(p[i]));
        }
	}
	return params.join("&");
};

/**
 * Tracks if an asyncRequest is in progress, allowing us to enforce a maximum
 * of one request at a time.
 */
BCNTRY.odatComm.connection = false;
	
/**
 * There are exceptions when an action like a vote must be executed right after
 * the page load so this will have to get executed even is BCNTRY.wall.connection
 * is true.
 */
BCNTRY.odatComm.exceptional_connection = false;


/**
 * Counts characters in an input (the reviewComments input to be specific)
 */
BCNTRY.odatComm.textCounter = function (field, remaining, maxLength) {
    var currentLength = field.value.length;
    if (currentLength > maxLength) {
        field.value = field.value.substring(0,maxLength);
		currentLength = maxLength;
    }
    if ($(remaining)) { $(remaining).innerHTML = maxLength - currentLength; }
};

/**
* Returns true or false if the user pressed the enter key in an textarea.
*/
BCNTRY.odatComm.checkEnter = function (e) {
    var characterCode; // literal character code will be stored in this variable

    if(e && e.which){ //if which property of event object is supported (NN4)
	e = e;
	characterCode = e.which; //character code is contained in NN4's which property
    }
    else{
	//e = event;
	characterCode = e.keyCode; //character code is contained in IE's keyCode property
    }

    if(characterCode == 13){ //if generated character code is equal to ascii 13 (if enter key)
	return false;
    } 
    else{ return true; }
};

/**
 * Turns on/off the main comment bubbles. If auto is true, then Omniture tracking will
 * be disabled for the current call.
 * @param {Boolean} status If true, bubbles will be enabled, disabled otherwise
 * @param {Boolean} auto If true, status is being set by some sort of automatic process
 */
BCNTRY.odatComm.turnOnOffCommentBubbles = function (status, auto) {
    if (status === '') { return; } 
	if (typeof status === 'undefined') {
		// Status wasn't passed in, assume toggle functionality.
		BCNTRY.odatComm.isCommentBubblesOn = BCNTRY.odatComm.isCommentBubblesOn == 1 ? 0 : 1;
		status = BCNTRY.odatComm.isCommentBubblesOn;
	}
    status = Boolean(parseInt(status, 10));
	if (status) {
		$('bubbles_up_down').style.display = "block";
		$('comments_off').style.backgroundPosition = "-26px -25px";
		$('comments_on').style.backgroundPosition = "0px top";
        BCNTRY.odatComm.isCommentBubblesOn = 1;
	}
	else {
		$('bubbles_up_down').style.display = "none";
		$('comments_off').style.backgroundPosition = "-26px top";
		$('comments_on').style.backgroundPosition = "0px -25px";
        BCNTRY.odatComm.isCommentBubblesOn = 0;
	}
    _setCookie('isCommentBubblesOn', BCNTRY.odatComm.isCommentBubblesOn, 365);

	if (!auto) {
		//omniture tracking
		BCNTRY.odat.scBuffer.trackToggleComments(status);
	}
};

/**
 * Clear field if text is still default text
 * @param {HTMLElement} An HTML form input element
 */
BCNTRY.odatComm.clearDefaultField = function (el) {
    var field = $(el);
    if(field.value == field.defaultValue) {
        field.value = '';
		field.style.color = "#000";
    }
};

/**
 * Restore default text in field
 * @param {HTMLElement} An HTML form input element
 */
BCNTRY.odatComm.restoreDefaultField = function (el) {
    var field = $(el);
    if(field.value === '' || field.value === field.defaultValue) {
        field.value = field.defaultValue;
		field.style.color = "#8f8f8f";
    }
};

if (typeof(BCNTRY.odatComm.widget) == 'undefined') { BCNTRY.odatComm.widget = {}; }

/**
 * Creates a ForgotPasswordPanel which is bound to the supplied HTMLElement, if supplied
 * @constructor
 *
 * @class
 * A modal panel that allows a user to reset their password if they forgot it.
 * @extends YAHOO.widget.Panel
 */
BCNTRY.odatComm.widget.ForgotPasswordPanel = function(el, args) { this.init(el, args); };

YAHOO.extend(BCNTRY.odatComm.widget.ForgotPasswordPanel, YAHOO.widget.Panel, {
	/**
	 * Initializes the panel.
	 */
	init: function(el, args) {
		if(typeof(args) == 'undefined') { args = {}; }
		args.modal = true;
		args.draggable = false;
		args.visible = false;
		args.context = ['forgot_link', 'br', 'tr', ['beforeShow'], [8,0]];
		BCNTRY.odatComm.widget.ForgotPasswordPanel.superclass.init.call(this, el, args);
	},

	/**
	 * Convenience function for checking if the given mouse coords are in the
	 * given container. Note: this function is duplicated like crazy. We need
	 * to move it somewhere globally accessible.
	 * @requires YAHOO.util.Dom
	 * @param {Array} mouse_coords A 2 element array, containing the X and Y coords of the mouse
	 * @param {HTMLElement} container An HTML element to test if the coords are contained within
	 * @returns {Boolean} True if the coords are contained within the container element
	 */
	_coordsInContainer: function(mouse_coords, container) {
		var region = yd.getRegion(container.id);
		return this._coordsInRegion(mouse_coords, region);
	},

	/**
	 * Convenience function for testing if the given mosue coords are contained
	 * within the given region.
	 * @requires YAHOO.util.Region
	 * @param {Array} mouse_coords A 2 element array, containing the X and Y coords of the mouse
	 * @param {YAHOO.util.Region} 
	 */
	_coordsInRegion: function(mouse_coords, region) {
		return (mouse_coords[1] >= region.top &&
			mouse_coords[0] >= region.left &&
			mouse_coords[1] <= region.bottom &&
			mouse_coords[0] <= region.right);
	},

	initEvents: function() {
		BCNTRY.odatComm.widget.ForgotPasswordPanel.superclass.initEvents.call(this);
		this.afterSuccess = new YAHOO.util.CustomEvent('afterSuccess', this);
	},

	render: function() {
		BCNTRY.odatComm.widget.ForgotPasswordPanel.superclass.render.call(this);
		
		//attach listeners
		var oThis = this;
		YAHOO.util.Dom.getElementsByClassName( 'submit', 'button', this.id, function(el) {
			YAHOO.util.Event.addListener(el, 'click', oThis.submit, oThis, true);
		});
		YAHOO.util.Dom.getElementsByClassName( 'close', null, this.id, function(el) {
			YAHOO.util.Event.addListener(el, 'click', oThis.hide, oThis, true);
		});
	},

	show: function() {
		this.setView('initial');
		BCNTRY.odatComm.widget.ForgotPasswordPanel.superclass.show.call(this);
		YAHOO.util.Event.addListener(document, 'mousedown', this.hideUnlessMouseInContainer, this, true);
	},

	hide: function() {
		YAHOO.util.Event.removeListener(document, 'mousedown', this.hide);
		BCNTRY.odatComm.widget.ForgotPasswordPanel.superclass.hide.call(this);
	},

	hideUnlessMouseInContainer: function(evt) {
		var mouse_coords = YAHOO.util.Event.getXY(evt);
		if(!this._coordsInContainer(mouse_coords, this.element)) {
			this.hide();
		}
	},

	getEmail: function() {
		return YAHOO.util.Dom.getElementsBy(
			function(el){return el.name === 'email';},
			'input',
			this.id)[0].value;
	},

	setView: function(view) {
		YAHOO.util.Dom.getElementsByClassName('view', null, this.element, function(el) {
			YAHOO.util.Dom.setStyle(el, 'display', YAHOO.util.Dom.hasClass(el, view) ? 'block' : 'none');
		});
	},

	submit: function() {
		var oThis = this;

		// only allow form to be submitted once
		var args = 'status=forgot_pass&mv_username=' + escape(this.getEmail());
		BCNTRY.odatComm.asyncRequest('POST', '/' + BCNTRY.site.catalog + '/content_upload/login.html', {
			success: function(resp) {
				//TODO: should do it this way, but I get the error "s is undefined" in the minified javascript
				//var obj = YAHOO.lang.JSON.parse(resp.resonseText);
				var obj; eval('obj = ' + resp.responseText);
				if(obj.sent_reminder && !obj.errors) {
					//console.log('Your password was reset. The new password has been sent to your email address.');
					oThis.setView('success');
				}
				else {
					//console.log({errors: obj.errors});
					oThis.setView('error');
				}
				oThis.afterSuccess.fire();
			},
			failure: function() {
				//console.log('There was a problem resetting your password. Please try again later');
				oThis.setView('error');
			}
		}, args);
	}
});

/**
 * @class
 * A help popup.
 */
BCNTRY.odatComm.widget.HelpPopup = function(el, args) { this.init(el, args); };

YAHOO.extend(BCNTRY.odatComm.widget.HelpPopup, YAHOO.widget.Overlay, {
        _coordsInContainer: function(mouse_coords, container) {
                var region = yd.getRegion(container.id);
                return this._coordsInRegion(mouse_coords, region);
        },
        _coordsInRegion: function(mouse_coords, region) {
                return (mouse_coords[1] >= region.top &&
                        mouse_coords[0] >= region.left &&
                        mouse_coords[1] <= region.bottom &&
                        mouse_coords[0] <= region.right);
        },
        init: function(el, args) {
                if (typeof(args) == 'undefined') { args = {}; }
                if (typeof(args.context) == 'undefined') { args.context = [el,"tl","tr"]; }
                if (typeof(args.visible) == 'undefined') { args.visible = false; }
                if (typeof(args.constraintoviewport) == 'undefined') { args.constraintoviewport = true; }
                BCNTRY.odatComm.widget.HelpPopup.superclass.init.call(this, el, args);
        },
        render: function() {
                BCNTRY.odatComm.widget.HelpPopup.superclass.render.call(this);
                // IE6 does not handle drop shadows well.  Need to add other class
                if (YAHOO.env.ua.ie == 6) {
                        yd.addClass(this.id, 'help_popup_ie6');
                }
                ye.on(this.id, 'mouseout', function(evt) {
                        var mouse_coords = ye.getXY(evt);
                        if ( ! this._coordsInContainer(mouse_coords, this) ) {
                                this.hide();
                        }
                }, this, true);
        }
});


/* Show a tool tip text when mouse is over */
BCNTRY.odatComm.HelpPopup = {
	helpPopup: null,
	ShowHelpPopup: function(target,description) {
		if ( !this.helpPopup ) {
			this.helpPopup = new BCNTRY.odatComm.widget.HelpPopup('help_template');
			this.helpPopup.render();
		}
		this.helpPopup.cfg.setProperty('context', [target,'tl','tr']);
		this.helpPopup.setBody('<p>' + description + '</p>');
		this.helpPopup.show();
	},
	HideHelpPopup: function(evt) {
		 var mouse_coords = ye.getXY(evt);
		 if ( this.helpPopup && !this._coordsInContainer(mouse_coords, this.helpPopup) ) {
			this.helpPopup.hide();
		 }
	},
	_coordsInContainer: function(mouse_coords, container) {
		var region = yd.getRegion(container.id);
		return this._coordsInRegion(mouse_coords, region);
	},
	_coordsInRegion: function(mouse_coords, region) {
		return (mouse_coords[1] >= region.top &&
				mouse_coords[0] >= region.left &&
				mouse_coords[1] <= region.bottom &&
				mouse_coords[0] <= region.right);
	 }
};

BCNTRY.odatComm.user = function() {
    this['class'] = 'User';
    this.id = null;
    this.reviewDisplayName = null;
    this.useRealName = false;

    return this;
};

BCNTRY.odatComm.user.currentUserSingleton = (function() {
        var instance = null;

        function PrivateConstructor() {
            this.loadFromCookies = function () {
                var username = _readCookie('username');
                if ( username ) {
                    this.id = username;     
                    this.reviewDisplayName = _readCookie('review_display_name');
					this.useRealName = _readCookie('use_real_name') == '1' ? true : false;
                    this.isLoggedIn = true;
                }
                else {
                    this.isLoggedIn = false;
                }
            };
        }
        
        // make this class essentially inherit from the standard user class.
        PrivateConstructor.prototype = new BCNTRY.odatComm.user();

		var myClosure = function() {
            this.getInstance = function() {
                if ( instance === null ) {
                    instance = new PrivateConstructor();
                    instance.constructor = null;
                    instance.loadFromCookies();
                }
                return instance;
            };
        };

        return new myClosure();
})();

BCNTRY.odatComm.showHelpText = function(element, hasGotFocusBefore) {
    if (hasGotFocusBefore == false && YAHOO.util.Dom.hasClass(theParent, 'login_error') == false) {
	var theParent = element.parentNode || element.parent;
	var helpTextElements = YAHOO.util.Dom.getElementsByClassName('helpText', 'span', theParent);

    	//Add the active class
    	yd.addClass(theParent, 'active');
    	//Add an ID to the helpText span and show it
    	helpTextElements[0].id = 'helpText_' + element.id;
    	yd.setStyle(helpTextElements[0].id, 'display', 'block');
    }
};

BCNTRY.odatComm.hideHelpText = function(element) {
    var theParent = element.parentNode || element.parent;
    var helpTextElements = yd.getElementsByClassName('helpText', 'span', theParent);
    //Remove the highlighting class
    yd.removeClass(theParent, 'active');
    //Hide the help text
    yd.setStyle(helpTextElements[0].id, 'display', 'none');
    helpTextElements[0].removeAttribute('id');
};

ye.onDOMReady(function() {
	// Load up a user on dom ready.
	BCNTRY.odatComm.currentUser = BCNTRY.odatComm.user.currentUserSingleton.getInstance();

	if (BCNTRY.page_data.instant_reviews_enabled) {
        	ye.addListener("toggleComments", "click", function () { BCNTRY.odatComm.turnOnOffCommentBubbles(); });

		if($("voteIt")) {
			BCNTRY.odatComm.showHelpText($('isThumbsUp'),false);
			// Only focus the textarea if no text has been entered
			ye.addListener('reviewThumbsUp', 'click', function() { 
				if($('reviewComment').value === $("reviewComment").defaultValue) { $('reviewComment').focus(); }
			});
			ye.addListener('reviewThumbsDown', 'click', function() {
			   if($('reviewComment').value === $("reviewComment").defaultValue) { $('reviewComment').focus(); }
			});
		};

		if ($("reviewComment")) {
			// Populate default fields for review comments
			$("reviewComment").defaultValue = "Care to explain?";
			if ($("reviewComment").value == "") {
				$("reviewComment").value = $("reviewComment").defaultValue;
			}
		};

		if($("firstName")) {
			// Add listeners to clear/restore default text
			ye.addListener('firstName', 'focus', function() { BCNTRY.odatComm.clearDefaultField(this); });
			ye.addListener('firstName', 'blur', function() { BCNTRY.odatComm.restoreDefaultField(this); });
		};

		if($('lastName')) { 
			// Add listeners to clear/restore default text
			ye.addListener('lastName', 'focus', function() { BCNTRY.odatComm.clearDefaultField(this); });
			ye.addListener('lastName', 'blur', function() { BCNTRY.odatComm.restoreDefaultField(this); });
		};

		if($("realFirstName")) {
			// Add listeners to clear/restore default text
			ye.addListener('realFirstName', 'focus', function() { BCNTRY.odatComm.clearDefaultField(this); });
			ye.addListener('realFirstName', 'blur', function() { BCNTRY.odatComm.restoreDefaultField(this); });
		}

		if($('realLastName')) {
			// Add listeners to clear/restore default text
			ye.addListener('realLastName', 'focus', function() { BCNTRY.odatComm.clearDefaultField(this); });
			ye.addListener('realLastName', 'blur', function() { BCNTRY.odatComm.restoreDefaultField(this); });
		}

		ye.addListener('reviewThumbsUp', 'mousedown', function() {
			BCNTRY.odatComm.setReviewThumbsUpDown(true);
		});
		ye.addListener('reviewThumbsDown', 'mousedown', function() {
			BCNTRY.odatComm.setReviewThumbsUpDown(false);
		});

		// Instant Review Form Stuff
		BCNTRY.odatComm.InstantReviewForm.current = BCNTRY.odatComm.initInstantReviewForm();
		ye.addListener('submit_vote', 'click', BCNTRY.odatComm.uploadReviewSubmitOnClick);

		BCNTRY.odatComm.InstantReviewForm.current.initFromCookie('instant_review_form_data');

		// if any of the form values are not their defaults, clear the default value settings
		yd.getElementsBy(
			function(el){ return (el.tagName == 'INPUT' || el.tagName == 'TEXTAREA'); },
			null,
			'review_form',
			function(el){ if(el.value != el.defaultValue) { el.style.color = '#000'; } }
		);

		// Create an instance of the InstantReviewManager and InstantReviewDomManager.
		BCNTRY.odatComm.instantReviewManager = new BCNTRY.InstantReviewManager();
		BCNTRY.odatComm.instantReviewDomManager = new BCNTRY.InstantReviewDomManager(
			BCNTRY.odatComm.instantReviewManager
		);
		BCNTRY.odatComm.updateDOM();

		//If an instant review was just posted, anchor to review wall, get latest, and show the one just posted
		var irCookieName = 'instant_review_just_posted';
		if (_readCookie(irCookieName)) {
			document.location.hash = 'dealTalk';
			var irObj = YAHOO.lang.JSON.parse(_readCookie(irCookieName));
			_deleteCookie(irCookieName, '/');
			BCNTRY.odatComm.instantReviewManager.fetchLatest(irObj);

			//omniture tracking
			BCNTRY.odat.scBuffer.trackInstantReviewFormSubmitted(
				irObj.isThumbsUp,
				irObj.user.useRealName);
		}
		BCNTRY.odatComm.turnOnOffCommentBubbles(_readCookie('isCommentBubblesOn'), true);

		//forgot password
		BCNTRY.odatComm.forgotPasswordPanel = new BCNTRY.odatComm.widget.ForgotPasswordPanel("forgot_pass_panel");
		BCNTRY.odatComm.forgotPasswordPanel.render();
		YAHOO.util.Event.addListener("forgot_link", "click", BCNTRY.odatComm.forgotPasswordPanel.show, BCNTRY.odatComm.forgotPasswordPanel, true);
		BCNTRY.odatComm.forgotPasswordPanel.afterSuccess.subscribe(function(){
			BCNTRY.odatComm.InstantReviewForm.current.setForgotPassMsg(true);
		});
	}
});
