
/***********************************************************
 * LiveBidModule.js
 * 
 * Defines the LiveBid objects and communication
 * protocol to make a successful connection
 * with fe.bidz.com
 * 
 ***********************************************************/

//status codes
var STATUS_INITIALIZED 		= "INITIALIZED";
var STATUS_STOPPED 			= "STOPPED";
var STATUS_STARTED 			= "STARTED";
var STATUS_IN_REQUEST 		= "IN_REQUEST";

var DEFAULT_POLLING_TIME    = 2; //seconds

var MAX_REQ                 = 1800;
var CURRENT_REQ_COUNT       = 0;

var MAX_ERROR_THRESHOLD     = 3;
var NO_ERROR = "OK";
var MAX_ERROR_REACHED = "MAX_ERRORS";

//LiveBid Server URL - coming from fe.bidz.com
//var LIVE_BID_URL			= "/cgi-bin/afelino.cgi?type=a&l=2&l1=20778641&bc1=0&t1=0&l2=20781086&bc2=0&t2=0&af=0";
//var LIVE_BID_URL			= "/static-web/liveBid/test_data.html";
var LIVE_BID_URL			= "/bzJApp/LiveProduct.action";  //expected query string: ?sid=100&tid=100&auctionId=10002

Object.size = function(obj) {
	var size = 0, key;

	for (key in obj) {
		if (obj.hasOwnProperty(key)) size++;
	}
	return size;
};

function LiveBidClient(sid, tid, statusCB) {
 	
	this.status = STATUS_INITIALIZED;
	this.requestQueryString = "sid=" + sid + "&tid=" + tid;
	this.finalURL = "";
	this.errorCount = 0;
	this.statusCallBack = statusCB;
	
	logToWindow("LiveBidClient starting query string is: " + this.requestQueryString);
	
	this.listingHash = {};
	
	//assign methods
	this.stop = stop;
	this.start = start;
	this.addListing = addListing;
	this.doRequest = doRequest;
	this.processOnComplete = processOnComplete;
	this.processResponse = processResponse;
	this.processResponseError = processResponseError;
	this.processLBResponse = processLBResponse;
	this.processOnException = processOnException;
	this.saveAuctionRecord = saveAuctionRecord;
	this.repeatRequest = repeatRequest;
	this.handleError = handleError;	
 } //END: LiveBidClient Constructor
 
 // function stop() will terminate communication with the server
 // and set the state of the client to STOPPED
 
 function stop(err) {
 	logToWindow("Stopping LBClient now.");
 	this.status = STATUS_STOPPED;
	//update the status
	currentLBClient = this;
	if (this.statusCallBack) this.statusCallBack(currentLBClient.status, err);
 } //END: stop()
 
 //function start() will start communication with fe.bidz.com
 //and register a call-back function to update all of the
 //listings.
 
 function start() {
 	
	//build the query string for the request, loop through the listing
	//hash and extract all of the listing ID's
	this.errorCount = 0; //reset the error count
	var listingsCommaSep = [];
	
//	for (var i=0; i<1; i++) {
	for (lid in this.listingHash) {
		listingsCommaSep.push(lid);
	} //loop through the listings
	
	this.finalURL = LIVE_BID_URL + "?" + this.requestQueryString + "&" + "auctionId=" + listingsCommaSep.join();
	
	logToWindow("Final URL: " + this.finalURL);
	
	currentLBClient = this;
		
	if (listingsCommaSep.length > 0 && (currentLBClient.status == STATUS_INITIALIZED || currentLBClient.status == STATUS_STOPPED))
	{
		//update the status
	 	this.status = STATUS_STARTED;
		logToWindow("LiveBid Client started. Polling time: " + DEFAULT_POLLING_TIME);
		if (currentLBClient.statusCallBack) currentLBClient.statusCallBack(currentLBClient.status, NO_ERROR);	
		repeatRequest(); //actually performs the request in a loop
	}
 } //END: start()
 

function repeatRequest() {
	
	//first let's check if a current request is running
	if (currentLBClient.status==STATUS_IN_REQUEST) {
		logToWindow("Not starting next request, current request running.");
		setTimeout("currentLBClient.repeatRequest()", 1000 * DEFAULT_POLLING_TIME * 2); //2 times longer delay in case of errors
		return;
	} //check if in request
	else if (currentLBClient.status==STATUS_STOPPED) {
		logToWindow("LBClient marked as stopped. Not setting new repeat timeout.");
		return
	}  //END: marked as stopped

	if (Object.size(currentLBClient.listingHash) > 0)	// doRequest() might have removed the last listing
	{
		currentLBClient.doRequest();
	}

	setTimeout("currentLBClient.repeatRequest()", 1000 * DEFAULT_POLLING_TIME);
		
} //END: repeatRequest()


//record the LBClient object for user later on, during call-backs
var currentLBClient = null;

// function doReqest() performs a single server call and registers
// a call-back function to absorb the response from the server
function doRequest() {
	
	currentLBClient.status = STATUS_IN_REQUEST;
	
	var urlWithCacheBuster = this.finalURL + "&cb=" + Math.random();
	
	logToWindow("About to create new Ajax request: " + urlWithCacheBuster);
	
	currentLBClient = this;
	$.ajax(
	{	url: urlWithCacheBuster,
		context: this,
		success: this.processResponse,
		error : this.processResponseError,
		complete : this.processOnComplete
	});
	
	CURRENT_REQ_COUNT = CURRENT_REQ_COUNT + 1;
	if (CURRENT_REQ_COUNT>=MAX_REQ) {
		//we have reached our limit, stop now
		logToWindow("Max requests of " + MAX_REQ + " reached. Stopping now.");
		currentLBClient.stop(NO_ERROR);
	}
} //END: doRequest

var CONST_RET_KEY_AN = "an"; //listing number
var CONST_RET_KEY_BC = "bc"; //bid count
var CONST_RET_KEY_WN = "wn"; //current winner
var CONST_RET_KEY_ET = "et"; //time left
var CONST_RET_KEY_NB = "nb"; //next bid
var CONST_RET_KEY_CB = "cb"; //current bid
var CONST_RET_KEY_CL = "cl"; //listings closed status (Y/N)
var CONST_RET_KEY_CT = "ct"; //close time formatted to site locale
var CONST_RET_KEY_AT = "at"; //auction type, could be E(english) or R(eserve)
var CONST_RET_KEY_RS = "rs"; //reserve status (met or not met)
var CONST_RET_KEY_LB = "lb"; //latest bidder
var CONST_RET_KEY_BI = "bi"; //latest bidder bid
var CONST_RET_KEY_WS = "ws"; //winSound
var CONST_RET_KEY_AI = "ai"; //avatar ID
var CONST_RET_KEY_BR = "<br>"; //record break

// function processLBResponse() is responsible for parsing and taking
// action on a successful LiveBid response
function processLBResponse (rs) {
	
	//we got a 2xx response, so let's reset the error count
	this.errorCount = 0; 
	
	//Let's split the entire results set by newline
	var nlArray = rs.split("\n");
	
	var local_an = -1;
	var local_bc = "";
	var local_wn = "";
	var local_et = "";
	var local_nb = "";
	var local_cb = "";
	var local_cl = "";
	var local_ct = "";
	var local_at = "";
	var local_rs = "";
	var local_lb = "";
	var local_bi = "";
	var local_ws = "";
	var local_ai = "";
		
	var bidCount = 0;
	
	for (j=0; j<nlArray.length; j++) {
		
		var rowStr = nlArray[j].trim();
		
		//logToWindow("Found row:" + rowStr);
		
		if (rowStr==CONST_RET_KEY_BR) {
			//logToWindow("Saving record.");
			if (currentLBClient.saveAuctionRecord(local_an, local_bc, local_wn, local_et, local_nb, local_cb, local_cl, local_ct, local_at, local_rs, local_lb,local_bi, local_ws, local_ai))
			{
				delete currentLBClient.listingHash[local_an];
				currentLBClient.start();
			}
			//check if a bid was placed
			var bidPlaced = wasBidPlaced(local_an);
			
			if (bidPlaced==true) {
				bidCount++;
			}
			
			continue; //for loop through rows
		} //found a record break <BR>

		var fieldNVP = nlArray[j].split("="); //fields split by an = sign
		var fieldKey = fieldNVP[0];
		var fieldValue = fieldNVP[1];
		
		//logToWindow("Key:"+ fieldKey);
		//logToWindow("Value:"+ fieldValue);
		
		//if key or value are undefined, we continue through
		if (fieldKey==null || fieldValue==null) {
			//logToWindow("null value found in key or value, continuing.");
			continue; //for loop
		} //null values
		else { //non-null values available
			fieldKey = fieldKey.trim();
			fieldValue = fieldValue.trim();
		}
	
		if (fieldKey==CONST_RET_KEY_AN) {
			local_an=fieldValue;
		}
		else if (fieldKey==CONST_RET_KEY_BC) {
			local_bc=fieldValue;
		}
		else if (fieldKey==CONST_RET_KEY_WN) {
			local_wn=fieldValue;
		}
		else if (fieldKey==CONST_RET_KEY_ET) {
			local_et=fieldValue;
		}
		else if (fieldKey==CONST_RET_KEY_NB) {
			local_nb=fieldValue;
		}
		else if (fieldKey==CONST_RET_KEY_CB) {
			local_cb=fieldValue;
		}
		else if (fieldKey==CONST_RET_KEY_CL) {
			local_cl=fieldValue;
		}
		else if (fieldKey==CONST_RET_KEY_CT) {
			local_ct=fieldValue;
		}
		else if (fieldKey==CONST_RET_KEY_AT) {
			local_at=fieldValue;
		}
		else if (fieldKey==CONST_RET_KEY_RS) {
			local_rs=fieldValue;
		}
		else if (fieldKey==CONST_RET_KEY_LB) {
			local_lb=fieldValue;
		}
		else if (fieldKey==CONST_RET_KEY_BI) {
			local_bi=fieldValue;
		}
		else if (fieldKey==CONST_RET_KEY_WS) {
			local_ws=fieldValue;
		}
		else if (fieldKey==CONST_RET_KEY_AI) {
			local_ai=fieldValue;
		}
		else {
			logToWindow("Unknown key found: " + fieldKey);
		} //key not found
		
	} //look through the results
	
	if (bidCount>0) {
		playDingSound();
	}
	
	//change the status back to running if it's already in the "in request state
	//otherwise, if the status is stopped in the middle of the request, we should
	//honor the stop request and not continue.
	if (currentLBClient.status==STATUS_IN_REQUEST) {
		currentLBClient.status = STATUS_STARTED;
	}
		
} //END: processLBResponse

function wasBidPlaced (auctNumber) {
	var lo = currentLBClient.listingHash[auctNumber];
	if (lo != null && lo.prevBid!=lo.currentBid) {
		return true;
	}
	else {
		return false;
	}
} //END: wasBidPlaced

/***
 * 
 * Save the listing information in the LB Client hashtable.
 * 
 * @param {String} an Auction Number
 * @param {String} bc Bid count
 * @param {String} wn Winning Bidder
 * @param {String} et Time Left (comma separated, dd,hh,mm,ss)
 * @param {String} nb Next Bid amount
 */
function saveAuctionRecord(an, bc, wn, et, nb, cb, cl, ct, at, rs, lb, bi, ws, ai) {
	
	
	//let's use the recorded LBClient as the call-back is no called
	//from that object. Can't use this.
	
	//logToWindow("looking for auction: " + an);
	//logToWindow("keys: " + currentLBClient.listingHash.keys());
	
	//first let's look up the an from the LB client has.
	var lo = currentLBClient.listingHash[an];
	
	//logToWindow("Found lo in hash:" + lo);
	
	if (lo==null) {
		logToWindow("Unable to find " + an + " in the listing hash.");
		return false;
	}

	//record the previous bid before it's overwritten	
	lo.prevBid = lo.currentBid;

	lo.currentBid = cb;
	lo.nextBid = nb;
	lo.currentWinner = wn;
	lo.numberOfBids = bc;
	lo.TimeLeft = et;
	lo.closedStatus = cl;
	lo.closeTime = ct;
	lo.auctionType = at;
	lo.reserveStatus = rs;
	lo.latestBidderName = lb;
	lo.latestBidderBid = bi;
	lo.winSound = ws;
	lo.avatarName = ai;
	
	if (lo.prevBid=="") {
		//We had an empty bid
		lo.prevBid = lo.currentBid;
	} //reset again
	
	//put the object back into hash
	currentLBClient.listingHash[an]=lo;
	
	//logToWindow("About to enter callback function.");
	
	//call the update function now to refresh the UI	
	return lo.callBackFunction(lo);
	
} //END: saveAuctionRecord

function processResponse(data, textStatus, xhr) {
	logToWindow("LB Response success: " + textStatus);
	this.processLBResponse(data);
} //END: processResponse
 
function processResponseError(xhr, textStatus, errorThrown) {
	logToWindow("LB Response Error: " + textStatus);
		currentLBClient.handleError();
	
} //END: processResponseError

function processOnComplete(xhr, textStatus) {
	//logToWindow("LB processOnComplete: status code:" + transport.status);
} //END: processOnComplete

function processOnException(Req, Ex1) {
	logToWindow("LB processOnException: " + Ex1);
	
	currentLBClient.handleError();
} //END: processOnComplete

function handleError() {
	//first we increment the total error count for the client
	currentLBClient.errorCount = currentLBClient.errorCount + 1;
	
	logToWindow("handleError() Error count: " + currentLBClient.errorCount);
	
	if (currentLBClient.errorCount>=MAX_ERROR_THRESHOLD) {
		//we need to stop the client
		currentLBClient.stop(MAX_ERROR_REACHED);
	}
	else {
		//change the status back to running
		currentLBClient.status = STATUS_STARTED;
	}
} //END: handleError

 // addListing() will add a listing and callback function to
 // the internal JS array.
 
 function addListing(listingID, callBackFunc) {
 	
	//let's create a new listingObject, populate it with the listing ID, callback function and null
	//for the remainder of the listing properties.
	
	if (listingID==null || listingID=="") {
		logToWindow("addListing: listingID is null. Returning.");
		return;
	}
	
	if (callBackFunc==null) {
		logToWindow("addListing: Call back function is null. Returning.");
		return;
	}
	
	logToWindow("Adding listing: " + listingID);
	
	var lo = new listingObject(listingID);
	
	//logToWindow("new lo created.");
	
	lo.callBackFunction = callBackFunc;
	
	//logToWindow("Adding new listingObject to hash.");
	
	this.listingHash[""+listingID]=lo;
	
 } //END: addListing()
 
 
 /*******************************************************************
  * listingObject :
  * 
  * stores the following properties
  * 
  * listingID :
  * callBackFunction :
  * currentBid :
  * currentWinner :
  * numberOfBids :
  * TimeLeft  :
  * prevBid :
  * closedStatus :
  * closeTime :
  * auctionType :
  * reserveStatus :
  * latestBidderName :
  * latestBidderBid :
  * 
  ********************************************************************/
 
 function listingObject(lid) {
 	this.listingID = lid;
	this.callBackFunction = null;
	this.currentBid = "";
	this.nextBid = "";
	this.currentWinner = "";
	this.numberOfBids = "";
	this.TimeLeft = "";
	this.prevBid = "";
	this.closedStatus = "";
	this.closeTime = "";
	this.auctionType = "";
	this.reserveStatus = "";
	this.latestBidderName = ""; 
	this.latestBidderBid = ""; 
	this.winSound = "";
	this.avatarName = "";
 } //END: listingObject constructor
 

/** trim() function taken from:
 *  http://www.codestore.net/store.nsf/unid/BLOG-20060313
 */ 
String.prototype.trim = function() {
return this.replace(/(?:(?:^|\n)\s+|\s+(?:$|\n))/g,"");
} //END: trim() 
 
 
