/**
 * CMC Markets Instrumet Finder 
 * The instrument finder retrieves markets, instrument lists and details for individual instruments
 * using JSON-RPC-Java to communicate with the server.
 * @author pmander
 * @requires properties_[lang].js
 * @version 1.5
 */

/** The global reference to the instrument finder object */
var instrumentFinder;

var DEFAULT_JSONRPC_PATH = "/JSON-RPC";

/** Specifies how often the InstrumentFinder should refresh displayed instruments */
var REFRESH_INTERVAL = 3000;
var CHANGE_HIGHLIGHT_DURATION = 1000;
/** Constant representing the html select element for the market category list */
var MARKET_LIST_NODE = "marketList";
/** Constant representing the html select element for the instrument list */
var INSTRUMENT_LIST_NODE = "instrumentList";
/** Constant representing the html tbody element for the list of select instruments */
var INSTRUMENT_BEANS_NODE = "selectedInstruments";
/** Constant representing the instrument search text box node */
var INSTRUMENT_SEARCH_NODE = "instrumentSearch";

/** Constanting representing an upward change in price direction */
var DIRECTION_UP = "up";
/** Constanting representing an downward change in price direction */
var DIRECTION_DOWN = "down";

/** CSS class names */
var CLASS_PRICEUP = "priceUp";
var CLASS_PRICEDOWN = "priceDown";
var CLASS_PRICESAME = "priceSame";

var CLASS_PRICEUP_HIGHLIGHTED = "priceUpHighlighted";
var CLASS_PRICEDOWN_HIGHLIGHTED = "priceDownHighlighted";

var EXCEPTION_LIMIT = 3;

var INSTRUMENT_FINDER = "instrumentFinder";

var CFD = "cfd";
var FX = "fx";
var SPREADBET = "spreadbet";


cmc.pricing = {};

/**
 * An instrument finder object
 * @constructor
 */
cmc.pricing.InstrumentFinder = function() 
{
	/*
	 * Fields
	 */
	this.jsonrpc = null;
	this.refreshInterval = null;
	this.consecutiveExceptionCount = 0;
	
	//the current JSON object containing the currently displayed instrument list
	this.currentInstrumentListBean;
	
	//an array of the instrument bean ids currently being displayed
	this.instrumentBeanIds = new Array();
	
	//the current price file being used to obtain prices
	this.priceFile = null;
};

/**
 * Initialises the InstrumentFinder.
 */
cmc.pricing.InstrumentFinder.prototype.init = function(product, jsonrpcPath)
{
	if(!jsonrpcPath)
	{
		jsonrpcPath = DEFAULT_JSONRPC_PATH;
	}
	this.jsonrpc = new JSONRpcClient(jsonrpcPath);
	
	this.loadMarketList(product);
	
	this.refreshInterval = window.setInterval("instrumentFinder.loadRefreshedInstrumentBeans()", REFRESH_INTERVAL);		
};

/**
 * Uses JSON-RPC-Java to load the market list.
 * It uses the callback function outputMarketList to output the response.
 * @return void
 */
cmc.pricing.InstrumentFinder.prototype.loadMarketList = function(product)
{
	var that = this;
	//get price xml files and descriptions
	this.jsonrpc.instrumentFinder.getAvailableFiles(function(response, exception) {
		//ajax callback
		if(exception)
		{
			throw new cmc.pricingInstrumentFinderException(exception.name, exception.code, exception.message, this);
		}
		else
		{
			that.consecutiveExceptionCount = 0;
		}
	
		var marketsMap = response.map;
		
		var numPriceFiles = 0;
		for(var key in marketsMap)
		{
			numPriceFiles++;
		}
		
		if(numPriceFiles > 1)
		{
			var marketListNode = document.getElementById(MARKET_LIST_NODE);
			
			for(var key in marketsMap)
			{		
				//add a new select option
				var option = document.createElement(ELEMENT_OPTION);
				option.setAttribute(ATT_VALUE, key);
				option.appendChild(document.createTextNode(marketsMap[key]));
				marketListNode.appendChild(option);
			}
		}
		else
		{
			for(var key in marketsMap) 
			{
				instrumentFinder.loadInstrumentList(key);
			}
		}
	}, product);	
};

/**
 * Uses JSON-RPC-Java to load the instrument list.
 * It uses the callback function outputInstrument List to output the response.
 * @param priceFile		The xml pricefile containing the instrument list to load
 * @return void
 */
cmc.pricing.InstrumentFinder.prototype.loadInstrumentList = function(priceFile)
{
	
	if(priceFile)
	{	
		this.priceFile = priceFile;
		var that = this;
		this.jsonrpc.instrumentFinder.getInstruments(function(response, exception) {
			//ajax callback
			if(exception)
			{
				throw new InstrumentFinderException(exception.name, exception.code, exception.message);
			}
			else
			{
				that.consecutiveExceptionCount = 0;
			}
		
			var instrumentListNode = document.getElementById(INSTRUMENT_LIST_NODE);
			
			//clear current list options
			removeSelectOptions(instrumentListNode, 0);
			//clear search text box
			var instrumentSearchNode = document.getElementById(INSTRUMENT_SEARCH_NODE);
			instrumentSearchNode.value = "";
			
			var instrumentListBean = response;
			var instrumentDescriptions = instrumentListBean.descAsList.list;
			var instrumentIds = instrumentListBean.pricesAslist.list;
			
			that.currentInstrumentListBean = instrumentListBean;
			
			for(var i = 0; i < instrumentDescriptions.length; i++)
			{
				var option = document.createElement(ELEMENT_OPTION);
				option.setAttribute(ATT_VALUE, instrumentIds[i]);
				option.appendChild(document.createTextNode(instrumentDescriptions[i]));
				instrumentListNode.appendChild(option);
			}
		}, priceFile);
	}
};

/**
 * Search the instrument list by the specified text
 * @param searchText	The text to filter with
 * @return void 
 */
cmc.pricing.InstrumentFinder.prototype.searchInstrumentList = function()
{
	var instrumentListBean = this.currentInstrumentListBean;

	if(instrumentListBean)
	{
		
		var instrumentListNode = document.getElementById(INSTRUMENT_LIST_NODE);
		var instrumentSearchNode = document.getElementById(INSTRUMENT_SEARCH_NODE);
		var searchText = instrumentSearchNode.value;
		
		//clear current list options
		removeSelectOptions(instrumentListNode, 0);
		
		var instrumentDescriptions = instrumentListBean.descAsList.list;
		var instrumentIds = instrumentListBean.pricesAslist.list;
		
		for(var i = 0; i < instrumentDescriptions.length; i++)
		{
			if(instrumentDescriptions[i].toLowerCase().indexOf(searchText.toLowerCase()) != -1)
			{
				var option = document.createElement(ELEMENT_OPTION);
				option.setAttribute(ATT_VALUE, instrumentIds[i]);
				option.appendChild(document.createTextNode(instrumentDescriptions[i]));
				instrumentListNode.appendChild(option);
			}
		}
	}
};

/**
 * Adds a new instrument finder bean to the InstrumentFinder
 * If the bean has not already been added, JSON-RPC-JAVA is used
 * to load the instrumentbean. outputInstrumentBean is then called to output
 * the details to the screen for the first time.
 * @instrumentId	The instrument id of the bean to add
 * @return void
 */
cmc.pricing.InstrumentFinder.prototype.addInstrumentBean = function(instrumentBeanId)
{
	//check the instrument bean isn't already displayed
	var alreadyAdded = false;
	for(var i = 0; i < this.instrumentBeanIds.length; i++)
	{
		if(this.instrumentBeanIds[i] == instrumentBeanId)
		{
			alreadyAdded = true
		}
	}
	
	if(!alreadyAdded)
	{
		var that = this;
		this.instrumentBeanIds.push(instrumentBeanId);
		this.jsonrpc.instrumentFinder.getInstrumentBean(function(response, exception) {
		//callback
			if(exception)
			{
				throw new InstrumentFinderException(exception.name, exception.code, exception.message);
			}
			else
			{
				this.consecutiveExceptionCount = 0;
			}
			
			var instrumentBean = response;
		
			var instrumentBeansNode = document.getElementById(INSTRUMENT_BEANS_NODE);
			
			//create table cells
			var tdLongDescription = document.createElement(ELEMENT_TD);
			var tdShortDescription = document.createElement(ELEMENT_TD);
			var tdBid = document.createElement(ELEMENT_TD);
			var tdOffer = document.createElement(ELEMENT_TD);
			var tdSpread = document.createElement(ELEMENT_TD);
			
			//apply style classes
			tdLongDescription.className = "description";
			tdShortDescription.className = "description";
			tdBid.className = "bid";
			tdOffer.className = "offer";	
			tdSpread.className = "spread";
			
			//append data to table cells
			tdLongDescription.appendChild(document.createTextNode(instrumentBean.longDescription));
			tdShortDescription.appendChild(document.createTextNode(instrumentBean.shortDescription));	
			tdBid.appendChild(document.createTextNode(instrumentBean.bid));
			tdOffer.appendChild(document.createTextNode(instrumentBean.offer));
			tdSpread.appendChild(document.createTextNode(instrumentBean.spread));
		
			//append table cells to new table row
			var trInstrument = document.createElement(ELEMENT_TR);
			trInstrument.id = instrumentBean.id;
			instrumentBeansNode.appendChild(trInstrument);
			trInstrument.appendChild(tdLongDescription);	
			trInstrument.appendChild(tdShortDescription);
			trInstrument.appendChild(tdBid);
			trInstrument.appendChild(tdOffer);
			trInstrument.appendChild(tdSpread);		
			
			//the remove button 
			var tdRemove = document.createElement(ELEMENT_TD);
			var buttonRemove = document.createElement(ELEMENT_BUTTON);
			var removeText = document.createTextNode(properties.remove);
			tdRemove.className="remove";
			buttonRemove.setAttribute(ATT_TITLE, properties.remove);
			buttonRemove.appendChild(removeText);
			tdRemove.appendChild(buttonRemove);
			trInstrument.appendChild(tdRemove);
			
			//remove button event handler
			buttonRemove.onclick = function () 
			{
				that.removeInstrumentBean(instrumentBean.id);
			};
			
			//show the "remove all" button
			var removeAllNode = document.getElementById("removeAll");
			removeAllNode.style.display = "block";
			
			//refresh
			this.loadRefreshedInstrumentBeans();
		
		
		}, instrumentBeanId, this.priceFile);
	}
};

/**
 * Removes the specified instrumentbean. Both internally and from the display.
 * @param removeId	The id of the instrument bean to remove
 * @return void
 */
cmc.pricing.InstrumentFinder.prototype.removeInstrumentBean = function(removeId)
{
	var instrumentBeanIds = this.instrumentBeanIds;

	//find and remove
	var idRemoved = false;
	for(var i = 0; i < instrumentBeanIds.length && idRemoved == false; i++)
	{
		if(instrumentBeanIds[i] == removeId)
		{
			this.instrumentBeanIds.splice(i, 1);
			idRemoved = true;
		}
	}

	var trInstrumentToRemove = document.getElementById(removeId);
	trInstrumentToRemove.parentNode.removeChild(trInstrumentToRemove);
	
	//hide the "remove all" button if no instruments are left
	if(instrumentBeanIds.length == 0)
	{
		var removeAllNode = document.getElementById("removeAll");
		removeAllNode.style.display = "none";
	}
};

/**
 * Removes all instrument beans currently being viewed, both internally and from the display
 * @return void
 */
cmc.pricing.InstrumentFinder.prototype.removeAllInstrumentBeans = function()
{
	var instrumentBeanIds = this.instrumentBeanIds;

	for(var i = 0; i < instrumentBeanIds.length; i++)
	{
		var trInstrumentToRemove = document.getElementById(instrumentBeanIds[i]);
		trInstrumentToRemove.parentNode.removeChild(trInstrumentToRemove);
	}
	
	this.instrumentBeanIds = new Array();
	
	//hide the "remove all" button
	var removeAllNode = document.getElementById("removeAll");
	removeAllNode.style.display = "none";
};

/**
 * Uses JSON-RPC-JAVA to reload all the current instrument beans belonging to this
 * InstrumentFinder object. Usees the callback method outputRefreshedInstrumentBeans
 * @return void
 */
cmc.pricing.InstrumentFinder.prototype.loadRefreshedInstrumentBeans = function()
{
	var instrumentBeanIds = this.instrumentBeanIds;
	
	if(instrumentBeanIds.length > 0)
	{
		var that = this;
		this.jsonrpc.instrumentFinder.getInstrumentBeans(function(response, exception) {
		
			if(exception)
			{
				throw new InstrumentFinderException(exception.name, exception.code, exception.message);
			}
			else
			{
				that.consecutiveExceptionCount = 0;
			}

			var refreshedInstrumentBeans = response;
		
			var BID_INDEX = 2;
			var OFFER_INDEX = 3;
			var SPREAD_INDEX = 4;
			
			//look for change in displayed instruments
			for(var i = 0; i < refreshedInstrumentBeans.list.length; i++)
			{
				var instrumentBean = refreshedInstrumentBeans.list[i];
				var instrumentBeanId = refreshedInstrumentBeans.list[i].id;
				var currentNodeToRefresh = document.getElementById(instrumentBeanId);
				
				if(currentNodeToRefresh)
				{
					//update bid
					var bidNode = currentNodeToRefresh.childNodes[BID_INDEX];
					
					//detect bid change
					var oldBid = bidNode.firstChild.nodeValue;
					var newBid = instrumentBean.bid;
					
					var bidChanged = false;
					if(newBid != oldBid)
					{
						bidChanged = true;
					}
							
					//update display with new bid
					bidNode.firstChild.nodeValue = newBid;
					
					if(instrumentBean.bidUp && !bidChanged)
					{
						bidNode.className = CLASS_PRICEUP;
					}
					else if(instrumentBean.bidUp && bidChanged)
					{
						that.highlightInstrument(bidNode, DIRECTION_UP);
						that.unHighlightInstrument(bidNode, DIRECTION_UP);
					}
					else if(instrumentBean.bidDown && !bidChanged)
					{
						bidNode.className = CLASS_PRICEDOWN;
					}
					else if(instrumentBean.bidDown && bidChanged)
					{
						that.highlightInstrument(bidNode, DIRECTION_DOWN);
						that.unHighlightInstrument(bidNode, DIRECTION_DOWN);
					}
							
					//update offer
					var offerNode = currentNodeToRefresh.childNodes[OFFER_INDEX];
					
					//detect offer change
					var oldOffer = offerNode.firstChild.nodeValue;
					var newOffer = instrumentBean.offer;
					
					var offerChanged = false;
					if(newOffer != oldOffer)
					{
						offerChanged = true;
					}		
			
					//update display with new offer
					offerNode.firstChild.nodeValue = newOffer;
			
					if(instrumentBean.offerUp && !offerChanged)
					{
						offerNode.className = CLASS_PRICEUP;
					}
					else if(instrumentBean.offerUp && offerChanged)
					{
						that.highlightInstrument(offerNode, DIRECTION_UP);
						that.unHighlightInstrument(offerNode, DIRECTION_UP);
					}
					else if(instrumentBean.offerDown && !offerChanged)
					{
						offerNode.className = CLASS_PRICEDOWN;
					}
					else if(instrumentBean.offerDown && offerChanged)
					{
						that.highlightInstrument(offerNode, DIRECTION_DOWN);
						that.unHighlightInstrument(offerNode, DIRECTION_DOWN);
					}
			
					//update spread
					var spreadNode = currentNodeToRefresh.childNodes[SPREAD_INDEX]
					spreadNode.firstChild.nodeValue = instrumentBean.spread;		
				}		
			}
		}
		, instrumentBeanIds, this.priceFile);
	}
};

/**
 * Highlights the selected instrument to signify a change in price
 * @param node the table cell html node to containing the bid or offer to highlight
 * @param direction the direction in which the bid or offer has changed. Use
 *					constants DIRECTION_UP or DIRECTION_DOWN
 * @return void
 */
cmc.pricing.InstrumentFinder.prototype.highlightInstrument = function(node, direction)
{
	if(direction == DIRECTION_UP)
	{
		node.className = CLASS_PRICEUP_HIGHLIGHTED;
	}
	else if(direction == DIRECTION_DOWN)
	{
		node.className = CLASS_PRICEDOWN_HIGHLIGHTED;
	}	
};

/**
 * Removes any highlighting of a bid or offer node after the duration specified in the constant
 * CHANGE_HIGHLIGHT_DURATION
 * @param node the table cell html node to containing the bid or offer to highlight
 * @param direction the direction in which the bid or offer has changed. Use
 *					constants DIRECTION_UP or DIRECTION_DOWN
 * @return void 
 */
cmc.pricing.InstrumentFinder.prototype.unHighlightInstrument = function(node, direction)
{
	window.setTimeout(function () 
	{
		if(direction == DIRECTION_UP)
		{
			node.className = CLASS_PRICEUP;
		}
		else if(direction == DIRECTION_DOWN)
		{
			node.className = CLASS_PRICEDOWN;
		}
	}, 
	CHANGE_HIGHLIGHT_DURATION);
};

/**
 * InstrumentFinder Exception
 * Outputs a name, code and message for the exception, typically from JSON-RPC
 * Exceptions are outputted as an alert (for debugging purposes) and as a Firefox Firebug 
 * console error
 * @param name	The exception's name
 * @param code	The exception's code
 * @param message	The excpetion's message
 * @constructor
 */
cmc.pricing.InstrumentFinderException = function(name, code, message, instrumentFinder)
{
	instrumentFinder.consecutiveExceptionCount++;

 	//firebug console error (only for firefox)
	if(navigator.userAgent.indexOf("Firefox") != -1)
	{	
		console.warn("Instrument Finder Exception\n" + 
		              "Name: " + name + "\n" +
             		  "Code: " + code + "\n" +
		              "Message: " + message);  
		console.info("Delaying price updates");

	}
	
	var refreshDelay = 30000;
	if(instrumentFinder.consecutiveExceptionCount < EXCEPTION_LIMIT)
	{
		window.clearInterval(instrumentFinder.refreshInterval);
		window.setTimeout(function() {
			instrumentFinder.refreshInterval = window.setInterval("instrumentFinder.loadRefreshedInstrumentBeans()", REFRESH_INTERVAL);				
		}, refreshDelay);
	}
	else
	{
		window.clearInterval(instrumentFinder.refreshInterval);
		
		var confirmMessage = properties.instrumentFinderTimeout;
						   
		var reloadInstrumentFinder = confirm(confirmMessage);
		
		if(reloadInstrumentFinder)
		{
			window.location.replace(window.location.href);
		}
	}
};

/**
 * Utility function for clearing select options from a select box
 * @param selectNode	the select node to remove options from
 * @param offSet		the offset in the options list at which to start removing options
 * @return void
 */
function removeSelectOptions(selectNode, offSet)
{		
	var numOptions = selectNode.options.length;

	//clear current options
	for(var i = 0; i < numOptions; i++)
	{
		selectNode.remove(offSet);
	}
}
