/****************************************************************************/
/* This object handles the display of information on the lowest fare finder */
/****************************************************************************/

// initialise the object
function LowestFareFinder(lowestFareFinderId, lowestFareFinderMinimisedId)
{
	this.lowestFareFinderId = lowestFareFinderId;
	this.lowestFareFinderMinimisedId = lowestFareFinderMinimisedId;
	
	this.outwardStartOffset = 0;
	this.inwardStartOffset = 0;
	
	this.showing = false;

	// set up constants for the display states
	this.normal = 0;
	this.highlight = 1;
	this.select = 2;
	this.inline = 3;

	// set up te prefix for the controls
	this.controlPrefix = "ctl00_mainContentPlaceHolder_lowestFareFinder_";
}

LowestFareFinder.prototype.Initialise = function()
{
	// get the element for the control if we haven't already initialised it
	if (this.lowestFareFinderElement == undefined)
	{
		// get all the elements so we don't have to find them again
		this.lowestFareFinderMinimisedElement = document.getElementById(this.lowestFareFinderMinimisedId);
		this.lowestFareFinderMinimisedInstructionsElement = YAHOO.util.Dom.getElementsByClassName("LowestFareFinderInstructions", undefined, this.lowestFareFinderMinimisedElement)[0];

		this.lowestFareFinderElement = document.getElementById(this.lowestFareFinderId);
		this.expandDiv = document.getElementById("expandDiv");

		this.gettingLowestFareData = YAHOO.util.Dom.getElementsByClassName("GettingLowestFareData", undefined, this.lowestFareFinderElement)[0];

		this.lowestFareFinderChoicePanel = YAHOO.util.Dom.getElementsByClassName("LFFChoicePanel", undefined, this.lowestFareFinderElement)[0];
		
		// initialise the fare arrays
		// get the fare rows
		this.fareRows = YAHOO.util.Dom.getElementsByClassName("LFFRow", undefined, this.lowestFareFinderElement);

		// and for each row, get the specific fares
		for (var fareRow in this.fareRows)
		{
			this.fareRows[fareRow].fares = YAHOO.util.Dom.getElementsByClassName("LFFFare", undefined, this.fareRows[fareRow]);

			for (fareColumn in this.fareRows[fareRow].fares)
			{
				var eventArgs = new Object();
				eventArgs.lowestFareFinder = this;
				eventArgs.row = fareRow * 1;
				eventArgs.column = fareColumn * 1;

				// set a rollover handler for each fare
				var success = YAHOO.util.Event.addListener(this.fareRows[fareRow].fares[fareColumn], "mouseover", this.MouseOver, eventArgs);
				success = YAHOO.util.Event.addListener(this.fareRows[fareRow].fares[fareColumn], "mouseout", this.MouseOut, eventArgs);
				success = YAHOO.util.Event.addListener(this.fareRows[fareRow].fares[fareColumn], "click", this.MouseClick, eventArgs);
			}			
		}

		this.columnTitle = YAHOO.util.Dom.getElementsByClassName("LFFBottomTab", undefined, this.lowestFareFinderElement);
		this.rowTitle = YAHOO.util.Dom.getElementsByClassName("LFFLeftTab", undefined, this.lowestFareFinderElement);

		var sliders = YAHOO.util.Dom.getElementsByClassName("LFFShowing", undefined, this.lowestFareFinderElement);

		this.outwardSlider = new LFFSlider(sliders[0], "outwardSliderId", "outwardSliderThumbId");
		this.outwardSlider.slider.subscribe("slideEnd", this.OutwardServiceChanged, this, true);
		
		this.inwardSlider = new LFFSlider(sliders[1], "returnSliderId", "returnSliderThumbId");
		this.inwardSlider.slider.subscribe("slideEnd", this.InwardServiceChanged, this, true);

		// set the elements for showing the details of the service
		this.outwardDepartureTime = document.getElementById("outwardDepartureTime");
		this.outwardArrivalTime = document.getElementById("outwardArrivalTime");
		this.outwardCost = document.getElementById("outwardCost");
		this.outwardCostValue = document.getElementById("outwardCostValue");
		this.outwardOnlineDiscount = document.getElementById("outwardOnlineDiscount");
		this.outwardTicketType = document.getElementById("outwardTicketType");
		this.outwardTicketInformationIFrame = document.getElementById("LFFoutwardTicketInformationIFrame");
		this.outwardTicketInformationIcon = document.getElementById(this.controlPrefix + "PopupOutwardTicketInfo");

		this.inwardDepartureTime = document.getElementById("inwardDepartureTime");
		this.inwardArrivalTime = document.getElementById("inwardArrivalTime");
		this.inwardCost = document.getElementById("inwardCost");
		this.inwardCostValue = document.getElementById("inwardCostValue");
		this.inwardOnlineDiscount = document.getElementById("inwardOnlineDiscount");
		this.inwardTicketType = document.getElementById("inwardTicketType");
		this.inwardTicketInformationIFrame = document.getElementById("LFFinwardTicketInformationIFrame");
		this.inwardTicketInformationIcon = document.getElementById(this.controlPrefix + "PopupReturnTicketInfo");

		this.totalCost = document.getElementById("totalCost");
		this.allPassengers = document.getElementById("allPassengers");

		this.btnSelectFare = document.getElementById(this.controlPrefix + "btnSelectFare");
	}
};

// Remember the selected services and fares
LowestFareFinder.prototype.SetSelectedServices = function(outwardService, inwardService, outwardFareGroup, inwardFareGroup)
{
	if (outwardService && outwardService.selected && inwardService && inwardService.selected)
	{
		this.selectedFare = new Object();

		this.selectedFare.selectedOutwardServiceId = outwardService.serviceId;
		this.selectedFare.selectedInwardServiceId = inwardService.serviceId;

		// remember the selected fare groups		
		this.selectedFare.selectedOutwardFareGroup = outwardFareGroup;
		this.selectedFare.selectedInwardFareGroup = inwardFareGroup;
	}
	else if (this.selectedFare != undefined)
	{
		delete this.selectedFare;
	}
};

// set the data from the mixing deck
LowestFareFinder.prototype.SetData = function(journeyPlanningData)
{
	// set the raildata
	this.journeyPlanningData = journeyPlanningData;

	this.NewDataAdded();
};

// called when the minimised lowest fare finder is rolled over
LowestFareFinder.prototype.Rollover = function(minimisedLowestFareFinder, bShow, e)
{
	// ensure the lowest fare finder is initialised
	this.Initialise();

	// if we're reolled over
	if (bShow)
	{
		// show the rolled over state
		YAHOO.util.Dom.addClass(this.lowestFareFinderMinimisedInstructionsElement, "LowestFareFinderInstructionsRollOver");
	}
	else
	{
		// if we're still inside this control then ignore the mouse out
		if (StillInsideElementOnMouseOut(minimisedLowestFareFinder, e))
		{
			return;
		}

		// don't show the rolled over state
		YAHOO.util.Dom.removeClass(this.lowestFareFinderMinimisedInstructionsElement, "LowestFareFinderInstructionsRollOver");
	}
};

// show the lowest fare finder in its maximum state
LowestFareFinder.prototype.ShowMaximised = function(bShow)
{
	var attributes;

	// ensure the lowest fare finder is initialised
	this.Initialise();

	// if this is a change
	if (bShow != this.showing)
	{
		var currentPosition = YAHOO.util.Dom.getRegion(this.lowestFareFinderMinimisedElement);

		if (bShow)
		{
			// start the animation to expand the lowest fare finder
			attributes =
			{
				top: { to: 137, from: currentPosition.top },
				left: { to: 178, from: currentPosition.left },
				width: { to: 649, from: currentPosition.right - currentPosition.left }, 
				height: { to: 516, from: currentPosition.bottom - currentPosition.top }
			};

			this.anim = new YAHOO.util.Anim(this.expandDiv, attributes, 0.2);

			this.anim.onComplete.subscribe(this.Show, this, true);

			this.expandDiv.style.top = currentPosition.top + "px";
			this.expandDiv.style.left = currentPosition.left + "px";
			this.expandDiv.style.width = (currentPosition.right - currentPosition.left) + "px";
			this.expandDiv.style.height = (currentPosition.bottom - currentPosition.top) + "px";

			mixingDeck.ShowTransparentDiv(true);
			
			SetVisibility(this.expandDiv, true);
			
			SetVisibility(this.lowestFareFinderMinimisedElement, false);
			
			this.anim.animate();
		}
		else
		{
			// start the animation to contract the lowest fare finder to its minimised position
			attributes =
			{
				top: { from: 137, to: currentPosition.top },
				left: { from: 178, to: currentPosition.left },
				width: { from: 649, to: currentPosition.right - currentPosition.left }, 
				height: { from: 516, to: currentPosition.bottom - currentPosition.top }
			};

			this.anim = new YAHOO.util.Anim(this.expandDiv, attributes, 0.4);

			this.anim.onComplete.subscribe(this.HideComplete, this, true);

			this.Hide();
			SetVisibility(this.expandDiv, true);
			
			this.anim.animate();
		}
	}
};

// show the lowest fare finder in its maximised state
LowestFareFinder.prototype.Show = function()
{
	SetVisibility(this.expandDiv, false);

	SetVisibility(this.lowestFareFinderElement, true);
	
	// this is a bug-fix for firefox on the mac to stop the scroll bars showing through
	if (BrowserDetect.browser == "Firefox" && BrowserDetect.OS == "Mac")
	{
		this.lowestFareFinderElement.style.overflow = "auto";
	}
	
	// show the ticket choice panel
	ShowElement(this.lowestFareFinderChoicePanel, true);
	
	this.showing = true;

	// we're not semi-selected by default. This may get changed in GetAllLowestFares
	this.semiSelected = false;

	// clear the details
	this.ClearSelectedFareDetails();

	// get the selected fare groups
	var selectedFareGroupOutward = this.journeyPlanningData._selectedFareGroup[outward].selected ? this.journeyPlanningData._selectedFareGroup[outward].fareGroup : null;
	var selectedFareGroupInward = this.journeyPlanningData._selectedFareGroup[inward].selected ? this.journeyPlanningData._selectedFareGroup[inward].fareGroup : null;

	// ignore the selected inward if this is a return
	if (selectedFareGroupOutward != null && selectedFareGroupOutward.isReturn)
	{
		selectedFareGroupInward = null;
	}

	// set the selected services
	this.SetSelectedServices(this.journeyPlanningData._selectedService[outward].service, this.journeyPlanningData._selectedService[inward].service, this.journeyPlanningData._selectedFareGroup[outward].fareGroup, this.journeyPlanningData._selectedFareGroup[inward].fareGroup);

	// and update the data that is shown on the screen
	this.UpdateData(true);
};

// called by the animation code when the minimising is compelte
LowestFareFinder.prototype.HideComplete = function()
{
	if (!ticketSearchControl.showing)
	{
		mixingDeck.ShowTransparentDiv(false);
	}
	SetVisibility(this.expandDiv, false);
	SetVisibility(this.lowestFareFinderMinimisedElement, true);
};

// hide the lowest fare finder
LowestFareFinder.prototype.Hide = function()
{
	// this is a bug-fix for firefox on the mac
	if (BrowserDetect.browser == "Firefox" && BrowserDetect.OS == "Mac")
	{
		this.lowestFareFinderElement.style.overflow = "";
	}

	SetVisibility(this.lowestFareFinderElement, false);
	
	ShowElement(this.lowestFareFinderChoicePanel, false);

	this.showing = false;
};

// this is called by the journey planning data when more rail data is received 
LowestFareFinder.prototype.NewDataAdded = function()
{
	if (this.showing)
	{
		// update the data that is shown on the screen
		this.UpdateData();
	}
};

// update the displayed data with the newly received data
// all lowest fares need to be recreated as there now might be a new lowest fare
LowestFareFinder.prototype.UpdateData = function(bChangePosition)
{
	// calculate the lowest fares for all the outward/inward combinations
	this.GetAllLowestFares();

	// handle the state where we have an undefined outward position or inward position for the selected fare
	if (this.selectedFare && (this.selectedFare.outwardPosition == undefined || this.selectedFare.inwardPosition == undefined))
	{
		delete this.selectedFare;
	}

	// if we're changing the position to the selected fare
	if (bChangePosition)
	{
		// set the outward position and set the inward position
		if (this.selectedFare)
		{
			this.outwardStartOffset = this.selectedFare.outwardPosition - 5;
			this.inwardStartOffset = this.selectedFare.inwardPosition - 5;
		}
		else
		{
			// nothing selected, so use requested date and time 
			this.outwardStartOffset = 0;
			for (var outwardServicePosition = 0; outwardServicePosition < this.journeyPlanningData.outwardServiceArray.length; outwardServicePosition++)
			{
				var outwardService = this.journeyPlanningData.outwardServiceArray[outwardServicePosition];
				
				if (mixingDeck.outwardDepartAfter)
				{
					// always increment so we're at the end time
					this.outwardStartOffset = outwardServicePosition;
					if (outwardService.dTime > mixingDeck.outwardDateTime)
					{
						break;
					}
				}
				else
				{
					// is the arrival time after the specified time
					if (outwardService.aTime > mixingDeck.outwardDateTime)
					{
						// yes, so move back one
						this.outwardStartOffset = outwardServicePosition - 1;
						break;
					}
				}
			}

			// for each inward service
			this.inwardStartOffset = 0;
			for (var inwardServicePosition = 0; inwardServicePosition < this.journeyPlanningData.inwardServiceArray.length; inwardServicePosition++)
			{
				var inwardService = this.journeyPlanningData.inwardServiceArray[inwardServicePosition];

				if (mixingDeck.returnDepartAfter)
				{
					// always increment so we're at the end time
					this.inwardStartOffset = inwardServicePosition;
					if (inwardService.dTime > mixingDeck.returnDateTime)
					{
						break;
					}
				}
				else
				{
					// is the arrival time after the specified time
					if (inwardService.aTime > mixingDeck.returnDateTime)
					{
						// yes, so move back one
						this.inwardStartOffset = inwardServicePosition - 1;
						break;
					}
				}
			}
		}
	}

	// and make sure they're not outside the data that we've got
	if (this.outwardStartOffset + 10 > this.journeyPlanningData.outwardServiceArray.length)
	{
		this.outwardStartOffset = this.journeyPlanningData.outwardServiceArray.length - 10;
	}

	if (this.outwardStartOffset < 5)
	{
		this.outwardStartOffset = 0;
	}
		
	if (this.inwardStartOffset + 10 > this.journeyPlanningData.inwardServiceArray.length)
	{
		this.inwardStartOffset = this.journeyPlanningData.inwardServiceArray.length - 10;
	}
	if (this.inwardStartOffset < 5)
	{
		this.inwardStartOffset = 0;
	}

	// if we're not changing the position
	if (!bChangePosition)
	{
		// find the position of where we were
		if (this.outwardStartService)
		{
			this.outwardStartOffset = this.FindService(this.journeyPlanningData.outwardServiceArray, this.outwardStartService);
		}
		
		if (this.inwardStartService)
		{
			this.inwardStartOffset = this.FindService(this.journeyPlanningData.inwardServiceArray, this.inwardStartService);
		}
	}

	// if we have a currently selected fare
	if (this.selectedFare)
	{
		// find the position of the selected service
		this.selectedFare.outwardPosition = this.FindService(this.journeyPlanningData.outwardServiceArray, this.selectedFare.outwardService);
		this.selectedFare.inwardPosition = this.FindService(this.journeyPlanningData.inwardServiceArray, this.selectedFare.inwardService);
	}
	
	// show the data and the scroll bars	
	this.ShowData();
	this.SetScrollerInformation();
};

// find the position of a service
LowestFareFinder.prototype.FindService = function(serviceArray, service)
{
	for (var iService = 0; iService < serviceArray.length; iService++)
	{
		if (service == serviceArray[iService])
		{
			return iService;
		}
	}
	// return undefined
};

// show the lowest fare data 
LowestFareFinder.prototype.ShowData = function()
{
	if (!this.showing)
	{
		return;
	}

	var row = 0;	

    this.journeyPlanningData.SetServicesOrder();
	// get the start position
	this.outwardStartService = this.journeyPlanningData.outwardServiceArray[this.outwardStartOffset];
	this.inwardStartService = this.journeyPlanningData.inwardServiceArray[this.inwardStartOffset];

	// for each outward service
	for (var outwardServicePosition = this.outwardStartOffset; outwardServicePosition < this.journeyPlanningData.outwardServiceArray.length; outwardServicePosition++)
	{
		// for each row that we're displaying
		if (row < 10)
		{
			// set the title for the row
			this.SetRowTitle(row, FormatTime(this.journeyPlanningData.outwardServiceArray[outwardServicePosition].dTime));

			var column = 0;

			// for each inward service
			for (var inwardServicePosition = this.inwardStartOffset; inwardServicePosition < this.journeyPlanningData.inwardServiceArray.length; inwardServicePosition++)
			{
				// for the column
				if (column < 10)
				{
					// set the column title
					if (row == 0 && this.journeyPlanningData.inwardServiceArray[inwardServicePosition])
					{
						this.SetColumnTitle(column, FormatTime(this.journeyPlanningData.inwardServiceArray[inwardServicePosition].dTime));
					}

					if (this.lowestFares[outwardServicePosition][inwardServicePosition])
					{					
						// set the details of the fare
						this.fareRows[row].fares[column].innerHTML = FormatPriceWithRounding(this.lowestFares[outwardServicePosition][inwardServicePosition].totFare);
						this.fareRows[row].fares[column].selected = (this.selectedFare && outwardServicePosition == this.selectedFare.outwardPosition && inwardServicePosition == this.selectedFare.inwardPosition);
						this.fareRows[row].fares[column].cheapest = (this.lowestFarePrice == this.lowestFares[outwardServicePosition][inwardServicePosition].totFare);
						this.SetFareAppearance(this.normal, row, column);

						if (this.fareRows[row].fares[column].selected)
						{
							// set the tab to be selected
							this.columnTitle[column].selected = true;
							this.SetBottomTabAppearance(false, column);

							this.rowTitle[row].selected = true;
							this.SetLeftTabAppearance(false, row);
						}
					}
					else
					{
						this.fareRows[row].fares[column].innerHTML = "";
						this.fareRows[row].fares[column].selected = false;
						this.fareRows[row].fares[column].cheapest = false;
						this.SetFareAppearance(this.normal, row, column);
					}
				}
				column++;
			}
		}
		row++;
	}

	// clear any unset data
	while (row < 10)
	{
		this.SetRowTitle(row, "");

		for (var tempColumn = 0; tempColumn < 10; tempColumn++)
		{
			this.fareRows[row].fares[tempColumn].innerHTML = "";
			this.fareRows[row].fares[tempColumn].selected = false;
			this.fareRows[row].fares[tempColumn].cheapest = false;
			this.SetFareAppearance(this.normal, row, tempColumn);
		}
		row++;
	}

	while (column < 10)
	{
		this.SetColumnTitle(column, "");

		for (var tempRow = 0; tempRow < 10; tempRow++)
		{
			this.fareRows[tempRow].fares[column].innerHTML = "";
			this.fareRows[tempRow].fares[column].selected = false;
			this.fareRows[tempRow].fares[column].cheapest = false;
			this.SetFareAppearance(this.normal, tempRow, column);
		}
		column++;
	}
};

// update the scroller information with the times or earlier/later if we don't know these times
LowestFareFinder.prototype.SetScrollerInformation = function()
{
	var outwardStartTime = "earlier";
	var inwardStartTime = "earlier";
	var outwardMidTime;
	var inwardMidTime;
	var outwardEndTime = "later";
	var inwardEndTime = "later";

	var bAllOutwardServicesReceived = false;
	var bAllInwardServicesReceived = false;

	// if we've received all the earlier services we can display the start time
	if (this.journeyPlanningData.AllDataReceived('O', 'E'))
	{
		outwardStartTime = FormatTime(this.journeyPlanningData.outwardServiceArray[0].dTime);
		
		if (this.journeyPlanningData.AllDataReceived('O', 'L'))
		{
			// we've got all the outward service
			totalOutwardServices = this.journeyPlanningData.outwardServiceArray.length;
			bAllOutwardServicesReceived = true;
		}
	}

	// if we've received all the earlier services we can display the end time
	if (this.journeyPlanningData.AllDataReceived('O', 'L'))
	{
		outwardEndTime = FormatTime(this.journeyPlanningData.outwardServiceArray[this.journeyPlanningData.outwardServiceArray.length - 1].dTime);
	}

	// if we've received all the earlier services we can display the start time
	if (this.journeyPlanningData.AllDataReceived('R', 'E'))
	{
		inwardStartTime = FormatTime(this.journeyPlanningData.inwardServiceArray[0].dTime);
		
		if (this.journeyPlanningData.AllDataReceived('R', 'L'))
		{
			// we've got all the inward service
			totalInwardServices = this.journeyPlanningData.inwardServiceArray.length;
			bAllInwardServicesReceived = true;
		}
	}

	// if we've received all the earlier services we can display the end time
	if (this.journeyPlanningData.AllDataReceived('R', 'L'))
	{
		inwardEndTime = FormatTime(this.journeyPlanningData.inwardServiceArray[this.journeyPlanningData.inwardServiceArray.length - 1].dTime);
	}

	inwardMidTime = FormatTime(this.journeyPlanningData.inwardServiceArray[Math.floor(this.journeyPlanningData.inwardServiceArray.length / 2)].dTime);
	outwardMidTime = FormatTime(this.journeyPlanningData.outwardServiceArray[Math.floor(this.journeyPlanningData.outwardServiceArray.length / 2)].dTime);

	// set the scrollers
	this.outwardSlider.SetShowing(Math.min(this.journeyPlanningData.outwardServiceArray.length, 10), this.journeyPlanningData.outwardServiceArray.length, bAllOutwardServicesReceived, outwardStartTime, outwardMidTime, outwardEndTime, this.outwardStartOffset);
	this.inwardSlider.SetShowing(Math.min(this.journeyPlanningData.inwardServiceArray.length, 10), this.journeyPlanningData.inwardServiceArray.length, bAllInwardServicesReceived, inwardStartTime, inwardMidTime, inwardEndTime, this.inwardStartOffset);
};

// called when the outward service slider changes
LowestFareFinder.prototype.OutwardServiceChanged = function()
{
	if (this.outwardSlider.thumbSet)
	{
		var outwardSliderValue = this.outwardSlider.GetValue();

		this.HandleDataExtension(outwardSliderValue, 'O');
		
		this.outwardStartOffset = (this.journeyPlanningData.outwardServiceArray.length - 10) * outwardSliderValue;
		this.outwardStartOffset = Math.max(0, Math.min(this.journeyPlanningData.outwardServiceArray.length - 10, Math.round(this.outwardStartOffset)));

		this.ShowData();
	}
};

// called when the inward service slider changes
LowestFareFinder.prototype.InwardServiceChanged = function()
{
	if (this.inwardSlider.thumbSet)
	{
		var inwardSliderValue = this.inwardSlider.GetValue();

		this.HandleDataExtension(inwardSliderValue, 'R');

		this.inwardStartOffset = (this.journeyPlanningData.inwardServiceArray.length - 10) * inwardSliderValue;
		this.inwardStartOffset = Math.max(0, Math.min(this.journeyPlanningData.inwardServiceArray.length - 10, Math.round(this.inwardStartOffset)));

		this.ShowData();
	}
};

// if the slider slides to the end of the data, we need to extend it with a call to the mixing deck
// this function encapsulates that behaviour
LowestFareFinder.prototype.HandleDataExtension = function(sliderValue, direction)
{
	// if we're at the begining
	if (sliderValue < 0.02)
	{
		// extend the data earlier
		if (mixingDeck.StartFTAEnquiryExtension(direction, "E"))
		{
			// and show the throbber
			this.ShowGettingLowestFareData(true);
		}
	}

	// if we're at the end
	if (sliderValue > 0.98)
	{
		// extend the data later
		if (mixingDeck.StartFTAEnquiryExtension(direction, "L"))
		{
			// and show the throbber
			this.ShowGettingLowestFareData(true);
		}
	}
};

// show the minimised lowest fare finder. The mixing deck is initialised with the minimided lowest fare finder hidden
// and when the data arrives and if it's non-open return, we show the minimised lowest fare finder
LowestFareFinder.prototype.ShowMinimised = function(bShow)
{
	this.Initialise();

	// if we're showing it and we should be showing it (i.e. it's a return and not an open return)
	if (bShow && this.journeyPlanningData && this.journeyPlanningData.data.isReturn && this.journeyPlanningData.data.returnDate && this.journeyPlanningData.outwardServiceArray.length > 0 && this.journeyPlanningData.inwardServiceArray.length > 0)
	{
		ShowElement(this.lowestFareFinderMinimisedElement, bShow);

		// position the minimised panel under the filter control
		var filterControl = document.getElementById("filterControl");
		if (filterControl)
		{
			var region = YAHOO.util.Dom.getRegion(filterControl);

			if (region.top)
			{
				YAHOO.util.Dom.setY(this.lowestFareFinderMinimisedElement, region.bottom + 10);
			}
		}
	}
};

// sets the title for a row
LowestFareFinder.prototype.SetRowTitle = function(fareRow, title)
{
	this.rowTitle[fareRow].selected = false;
	this.rowTitle[fareRow].innerHTML = title;
	this.SetLeftTabAppearance(false, fareRow);
};

// sets the title for a column
LowestFareFinder.prototype.SetColumnTitle = function(fareColumn, title)
{
	this.columnTitle[fareColumn].selected = false;
	this.columnTitle[fareColumn].innerHTML = title;
	this.SetBottomTabAppearance(false, fareColumn);
};

// calculate the lowest fares for all inward/outward service combinations
LowestFareFinder.prototype.GetAllLowestFares = function()
{
	// we don't currently knwo what the absolute lowest fare is
	this.lowestFarePrice = undefined;

	// create an array of lowest fares arrays
	this.lowestFares = new Array();

	// for each outward service
	for (var outwardServicePosition = 0; outwardServicePosition < this.journeyPlanningData.outwardServiceArray.length; outwardServicePosition++)
	{
		// get the service
		var outwardService = this.journeyPlanningData.outwardServiceArray[outwardServicePosition];

		// create an array for the lowest fares fro this service
		this.lowestFares[outwardServicePosition] = new Array();
	
		// for each inward service
		for (var inwardServicePosition = 0; inwardServicePosition < this.journeyPlanningData.inwardServiceArray.length; inwardServicePosition++)
		{
			// get the service
			var inwardService = this.journeyPlanningData.inwardServiceArray[inwardServicePosition];

			// if the outward service arrives after the inward service departs
			if (outwardService.aTime > inwardService.dTime)
			{
				// then we can't catch it, so move onto the next service
				continue;
			}

			// get the lowest single fare on the outward service
			var outwardServiceFare = outwardService.serviceFares[outwardService.lowestSingle];

			// get the lowest single fare on the inward service
			var inwardServiceFare = inwardService.serviceFares[inwardService.lowestSingle];

			if (outwardServiceFare == undefined || inwardServiceFare == undefined)
			{
				// no valid single fare, so use the cheapest valid return fare (which may be null)
				outwardServiceFare = this.GetCheapestValidServiceFare(outwardService, inwardService, 999999);
				inwardServiceFare = undefined;
			}
			else
			{
				// get the cheapest single fare
				var cheapestSingleFares = outwardServiceFare.totFare + inwardServiceFare.totFare;

				// is there a cheaper return
				var cheapestValidReturnFareGroup = this.GetCheapestValidServiceFare(outwardService, inwardService, cheapestSingleFares);
				if (cheapestValidReturnFareGroup && cheapestValidReturnFareGroup.totFare <= cheapestSingleFares)
				{
					// set the fare group
					outwardServiceFare = cheapestValidReturnFareGroup;
					inwardServiceFare = undefined;
				}
			}

			// outwardServiceFare and inwardServiceFare now contain the cheapest fare
			if (inwardServiceFare)
			{
				// it's a single, so add up the price
				this.lowestFares[outwardServicePosition][inwardServicePosition] = new Object();

				this.lowestFares[outwardServicePosition][inwardServicePosition].totFare = outwardServiceFare.totFare + inwardServiceFare.totFare;
				this.lowestFares[outwardServicePosition][inwardServicePosition].outwardServiceFare = outwardServiceFare;
				this.lowestFares[outwardServicePosition][inwardServicePosition].inwardServiceFare = inwardServiceFare;

				// set the lowest price				
				if (this.lowestFarePrice == undefined || this.lowestFarePrice > this.lowestFares[outwardServicePosition][inwardServicePosition].totFare)
				{
					this.lowestFarePrice = this.lowestFares[outwardServicePosition][inwardServicePosition].totFare;
				}

				// is this fare selected
				this.lowestFares[outwardServicePosition][inwardServicePosition].selected = (this.selectedFare && outwardService.serviceId == this.selectedFare.selectedOutwardServiceId && inwardService.serviceId == this.selectedFare.selectedInwardServiceId);
				if (this.lowestFares[outwardServicePosition][inwardServicePosition].selected)
				{
					// if we've not selected this specific fare then set the state as 'semi-selected'
					if (!this.selectedFare.selectedOutwardFareGroup || this.selectedFare.selectedOutwardFareGroup.fareGroupId != outwardServiceFare.fgId || !this.selectedFare.selectedInwardFareGroup || this.selectedFare.selectedInwardFareGroup.fareGroupId != inwardServiceFare.fgId)
 					{
						this.semiSelected = true;
					}

					this.SetSelectedDetails(outwardServicePosition, inwardServicePosition, this.semiSelected);
				}
			}
			else
			{
				// it's a return or not set
				if (outwardServiceFare)
				{
					// it's a return
					this.lowestFares[outwardServicePosition][inwardServicePosition] = new Object();

					this.lowestFares[outwardServicePosition][inwardServicePosition].totFare = outwardServiceFare.totFare;
					this.lowestFares[outwardServicePosition][inwardServicePosition].outwardServiceFare = outwardServiceFare;

					// set the lowest price
					if (this.lowestFarePrice == undefined || this.lowestFarePrice > this.lowestFares[outwardServicePosition][inwardServicePosition].totFare)
					{
						this.lowestFarePrice = this.lowestFares[outwardServicePosition][inwardServicePosition].totFare;
					}

					// is this fare selected
					this.lowestFares[outwardServicePosition][inwardServicePosition].selected = (this.selectedFare && outwardService.serviceId == this.selectedFare.selectedOutwardServiceId && inwardService.serviceId == this.selectedFare.selectedInwardServiceId);
					if (this.lowestFares[outwardServicePosition][inwardServicePosition].selected)
					{
						// if we've not selected this specific fare then set the state as 'semi-selected'
						if (!this.selectedFare.selectedOutwardFareGroup || this.selectedFare.selectedOutwardFareGroup.fareGroupId != outwardServiceFare.fgId)
						{
							this.semiSelected = true;
						}

						this.SetSelectedDetails(outwardServicePosition, inwardServicePosition, this.semiSelected);
					}
				}
			}
		}
	}
};

// get the cheapest return fare available on the services
// we're only interested in cheap fares, so if the search for the cheapest goes about the cost of the two singles
// we might as well give up
LowestFareFinder.prototype.GetCheapestValidServiceFare = function(outwardService, inwardService, cheapestSingleFares)
{
	// get the lowest return fare on the outward service
	var lowestServiceFareId = 0;

	// find the cheapest return fare group. The fares are sorted cheapest first
	while (true)
	{	
		// have we reached the end of the list
		var outwardReturnServiceFare = outwardService.serviceFares[lowestServiceFareId];
		if (outwardReturnServiceFare == undefined)
		{
			return null;
		}

		// is it more expensive
		if (outwardReturnServiceFare.totFare > cheapestSingleFares)
		{
			// we looking at these cheapest first. If this is more expensive than the two singles then we won't find a cheaper one, so stop looking
			return null;
		}

		// check to see if the lowest fare on the outward service is valid on the inward service
		if (this.journeyPlanningData.data.outwardFareGroups[outwardReturnServiceFare.fgId] && this.journeyPlanningData.data.outwardFareGroups[outwardReturnServiceFare.fgId].isReturn && this.IsFareGroupValid(outwardReturnServiceFare, inwardService))
		{
			return outwardReturnServiceFare;
		}
		else
		{			
			// find the next cheapest fare group
			lowestServiceFareId++;
		}
	}
};

// is a service fare valid for an inward service
LowestFareFinder.prototype.IsFareGroupValid = function(outwardReturnServiceFare, inwardService)
{
	for (var iServiceFare in inwardService.serviceFares)
	{
		// valid if the fareGroupId is in the inward service fares
		if (inwardService.serviceFares[iServiceFare].fgId == outwardReturnServiceFare.fgId)
		{
			return true;
		}
	}
	// we didn't find the service fare so it's not valid
	return false;
};

// show or hide the 'please wait' item
LowestFareFinder.prototype.ShowGettingLowestFareData = function(show)
{
	if (this.gettingLowestFareData)
	{
		ShowElement(this.gettingLowestFareData, show);
	}
};

// called when the mouse hovers over a fare
LowestFareFinder.prototype.MouseOver = function(e, eventArgs)
{
	eventArgs.lowestFareFinder.SetAppearance(true, eventArgs.row, eventArgs.column);
};

// called when the mouse is no longer hovering over a fare
LowestFareFinder.prototype.MouseOut = function(e, eventArgs)
{
	// if we're still inside this fare then ignore the mouse out
	if (StillInsideElementOnMouseOut(eventArgs.lowestFareFinder.fareRows[eventArgs.row].fares[eventArgs.column], e))
	{
		return;
	}

	eventArgs.lowestFareFinder.SetAppearance(false, eventArgs.row, eventArgs.column);
};

// called when the user clicks on a fare
LowestFareFinder.prototype.MouseClick = function(e, eventArgs)
{
	var outwardPosition;
	var inwardPosition;
	
	var lowestFareFinder = eventArgs.lowestFareFinder;
	var fare = lowestFareFinder.fareRows[eventArgs.row].fares[eventArgs.column];

	var fareSelected = fare.selected;

	// clear the previous selected fare if there is one
	if (lowestFareFinder.selectedFare)
	{
		if (lowestFareFinder.lowestFares[lowestFareFinder.selectedFare.outwardPosition][lowestFareFinder.selectedFare.inwardPosition])
		{
			lowestFareFinder.lowestFares[lowestFareFinder.selectedFare.outwardPosition][lowestFareFinder.selectedFare.inwardPosition].selected = false;
		}

		outwardPosition = lowestFareFinder.selectedFare.outwardPosition - lowestFareFinder.outwardStartOffset;
		inwardPosition = lowestFareFinder.selectedFare.inwardPosition - lowestFareFinder.inwardStartOffset;

		if (outwardPosition >= 0 && outwardPosition < 10 && inwardPosition >= 0 && inwardPosition < 10)
		{
			lowestFareFinder.fareRows[outwardPosition].fares[inwardPosition].selected = false;
			lowestFareFinder.SetAppearance(false, outwardPosition, inwardPosition);

			lowestFareFinder.columnTitle[inwardPosition].selected = false;
			lowestFareFinder.SetBottomTabAppearance(false, inwardPosition);

			lowestFareFinder.rowTitle[outwardPosition].selected = false;
			lowestFareFinder.SetLeftTabAppearance(false, outwardPosition);
		}

		delete lowestFareFinder.selectedFare;
	}

	// the fare is either not selected or semi-selected. Semi-selected means that the user has selected this
	// outward and retrun service in the mixing-deck, but the fare selected is not the cheapest fare
	if (!fareSelected || lowestFareFinder.semiSelected)
	{
		lowestFareFinder.semiSelected = false;
	
		outwardPosition = eventArgs.row + lowestFareFinder.outwardStartOffset;
		inwardPosition = eventArgs.column + lowestFareFinder.inwardStartOffset;

		// if there's a fare
		if (lowestFareFinder.lowestFares[outwardPosition] != undefined && lowestFareFinder.lowestFares[outwardPosition][inwardPosition] != undefined)
		{
			fare.selected = true;

			lowestFareFinder.selectedFare = new Object();

			lowestFareFinder.selectedFare.selectedOutwardServiceId = lowestFareFinder.journeyPlanningData.outwardServiceArray[outwardPosition].serviceId;
			lowestFareFinder.selectedFare.selectedInwardServiceId = lowestFareFinder.journeyPlanningData.inwardServiceArray[inwardPosition].serviceId;

			lowestFareFinder.columnTitle[eventArgs.column].selected = true;
			lowestFareFinder.SetBottomTabAppearance(false, eventArgs.column);

			lowestFareFinder.rowTitle[eventArgs.row].selected = true;
			lowestFareFinder.SetLeftTabAppearance(false, eventArgs.row);
			
			lowestFareFinder.SetSelectedDetails(outwardPosition, inwardPosition, false);
		}
		else
		{
			lowestFareFinder.SetSelectedDetails();
		}
	}
	else
	{
		// clear this fare
		fare.selected = false;
		lowestFareFinder.SetSelectedDetails();
		DisableWTButton(lowestFareFinder.btnSelectFare, true);
	}

	lowestFareFinder.SetAppearance(true, eventArgs.row, eventArgs.column);
};

// set the appearance of a fare element and the ones on the same row and column
LowestFareFinder.prototype.SetAppearance = function(highlight, row, column)
{
	// set this item
	if (highlight)
	{
		this.SetFareAppearance(this.highlight, row, column);
	}
	else
	{
		this.SetFareAppearance(this.normal, row, column);
	}

	// set the ones on the same column
	for (var rowToSet = 0; rowToSet < 10; rowToSet++)
	{
		if (rowToSet != row)
		{
			if (highlight)
			{
				this.SetFareAppearance(this.inline, rowToSet, column);
			}
			else
			{
				this.SetFareAppearance(this.normal, rowToSet, column);
			}
		}
	}

	// set the ones on the same row
	for (var columnToSet = 0; columnToSet < 10; columnToSet++)
	{
		if (columnToSet != column)
		{
			if (highlight)
			{
				this.SetFareAppearance(this.inline, row, columnToSet);
			}
			else
			{
				this.SetFareAppearance(this.normal, row, columnToSet);
			}
		}
	}
	
	this.SetBottomTabAppearance(highlight, column);
	this.SetLeftTabAppearance(highlight, row);
};

// set the appearance of the bottom tab
LowestFareFinder.prototype.SetBottomTabAppearance = function(appearance, column)
{
	if (appearance == this.highlight)
	{
		this.columnTitle[column].className = "LFFBottomTabHighlight";
	}
	else if (this.columnTitle[column].selected)
	{
		this.columnTitle[column].className = "LFFBottomTabSelected";
	}
	else
	{
		this.columnTitle[column].className = "LFFBottomTab";
	}
};

// set the appearance of the left tab
LowestFareFinder.prototype.SetLeftTabAppearance = function(appearance, row)
{
	if (appearance == this.highlight)
	{
		this.rowTitle[row].className =  "LFFLeftTabHighlight";
	}
	else if (this.rowTitle[row].selected)
	{
		this.rowTitle[row].className =  "LFFLeftTabSelected";
	}
	else 
	{
		this.rowTitle[row].className =  "LFFLeftTab";
	}
};

// set the appearance of a fare
LowestFareFinder.prototype.SetFareAppearance = function(appearance, row, column)
{
	var outwardPosition = row + this.outwardStartOffset;
	var inwardPosition = column + this.inwardStartOffset;

	var className;

	// if there's a fare
	if (this.fareRows[row].fares[column].selected)
	{
		if (this.semiSelected)
		{
			className = "SemiSelected";
		}
		else
		{
			iBackgroundPositionX = 2;

			if (appearance == this.highlight)
			{
				className = "SelectedHighlight";
			}
			else
			{
				className = "Selected";
			}
		}
	}
	else if (this.lowestFares[outwardPosition] != undefined && this.lowestFares[outwardPosition][inwardPosition] != undefined)
	{
		if (this.fareRows[row].fares[column].cheapest)
		{
			iBackgroundPositionX = 0;

			if (appearance == this.inline)
			{
				className = "CheapestInline";
			}
			else if (appearance == this.highlight)
			{
				className = "CheapestHighlight";
			}
			else
			{
				className = "Cheapest";
			}
		}
		else
		{
			if (appearance == this.inline)
			{
				className = "Inline";
			}
			else if (appearance == this.highlight)
			{
				className = "Highlight";
			}
			else
			{
				className = "Normal";
			}
		}
	}
	else
	{
		// no fare 
		if (appearance == this.inline)
		{
			className = "NoFareInline";
		}
		else if (appearance == this.highlight)
		{
			className = "NoFareHighlight";
		}
		else
		{
			className = "";
		}
	}
	
	this.fareRows[row].fares[column].className = "LFFFare" + className;
};

// remember which fare is selected
LowestFareFinder.prototype.SetSelectedDetails = function(outwardPosition, inwardPosition, semiSelected)
{
	// if we haven't got a selected fare
	if (outwardPosition == undefined || inwardPosition == undefined)
	{
		// clear the details
		if (this.selectedFare)
		{
			delete this.selectedFare;
		}

		this.ClearSelectedFareDetails();
	
	}
	else
	{
		// remember this fare
		this.selectedFare = new Object();
		this.selectedFare.outwardPosition = outwardPosition;
		this.selectedFare.inwardPosition = inwardPosition;

		this.selectedFare.outwardService = this.journeyPlanningData.outwardServiceArray[outwardPosition];
		this.selectedFare.inwardService = this.journeyPlanningData.inwardServiceArray[inwardPosition];

		if (!semiSelected)
		{
			// we have a selected fare so enable the select fare button
			DisableWTButton(this.btnSelectFare, false);

			// set the details
			this.outwardDepartureTime.innerHTML = FormatTime(this.journeyPlanningData.outwardServiceArray[outwardPosition].dTime);
			this.outwardArrivalTime.innerHTML =  FormatTime(this.journeyPlanningData.outwardServiceArray[outwardPosition].aTime);
			this.outwardCostValue.innerHTML = FormatPrice(this.lowestFares[outwardPosition][inwardPosition].outwardServiceFare.totFare);
			SetVisibility(this.outwardOnlineDiscount, false);
			
			var fareGroupId = this.lowestFares[outwardPosition][inwardPosition].outwardServiceFare.fgId;
			this.outwardTicketType.innerHTML = this.journeyPlanningData.data.outwardFareGroups[fareGroupId].desc;

			this.inwardDepartureTime.innerHTML = FormatTime(this.journeyPlanningData.inwardServiceArray[inwardPosition].dTime);
			this.inwardArrivalTime.innerHTML = FormatTime(this.journeyPlanningData.inwardServiceArray[inwardPosition].aTime);

			// set the ticket information pop-up
			this.outwardTicketInformationIFrame.deferedSrc = this.journeyPlanningData.data.fares[this.lowestFares[outwardPosition][inwardPosition].outwardServiceFare.fareIds[0]].ttcUrl;
			SetVisibility(this.outwardTicketInformationIcon, true);
			SetVisibility(this.outwardOnlineDiscount, this.journeyPlanningData.data.fares[this.lowestFares[outwardPosition][inwardPosition].outwardServiceFare.fareIds[0]].isDiscounted);

			// if we have an inward fare (i.e it's two singles)
			if (this.lowestFares[outwardPosition][inwardPosition].inwardServiceFare)
			{
				SetVisibility(this.inwardCost, true);
				this.inwardCostValue.innerHTML = FormatPrice(this.lowestFares[outwardPosition][inwardPosition].inwardServiceFare.totFare);

				fareGroupId = this.lowestFares[outwardPosition][inwardPosition].inwardServiceFare.fgId;
				this.inwardTicketType.innerHTML = this.journeyPlanningData.data.inwardFareGroups[fareGroupId].desc;

				this.inwardTicketInformationIFrame.deferedSrc = this.journeyPlanningData.data.fares[this.lowestFares[outwardPosition][inwardPosition].inwardServiceFare.fareIds[0]].ttcUrl;
				SetVisibility(this.inwardTicketInformationIcon, true);
				SetVisibility(this.inwardOnlineDiscount, this.journeyPlanningData.data.fares[this.lowestFares[outwardPosition][inwardPosition].inwardServiceFare.fareIds[0]].isDiscounted);
				
				this.lowestFares[outwardPosition][inwardPosition].inwardServiceFare.fareIds[0].ttcUrl;
			}
			else
			{
				// it's a return
				SetVisibility(this.inwardTicketInformationIcon, false);
				SetVisibility(this.inwardCost, false);
				this.inwardTicketType.innerHTML = "";
				SetVisibility(this.inwardOnlineDiscount, false);
			}

			// show the price
			this.totalCost.innerHTML = FormatPrice(this.lowestFares[outwardPosition][inwardPosition].totFare);
			SetVisibility(this.allPassengers, this.journeyPlanningData.data.multiplePassengers);
		}
	}
};

// clear the selected details
LowestFareFinder.prototype.ClearSelectedFareDetails = function()
{
	DisableWTButton(this.btnSelectFare, true);

	// clear the details
	this.outwardDepartureTime.innerHTML = "";
	this.outwardArrivalTime.innerHTML =  "";
	this.outwardCostValue.innerHTML = "&pound;0.00";
	SetVisibility(this.outwardOnlineDiscount, false);
	this.outwardTicketType.innerHTML = "";

	this.inwardDepartureTime.innerHTML = "";
	this.inwardArrivalTime.innerHTML = "";
	SetVisibility(this.inwardCost, true);
	this.inwardCostValue.innerHTML = "&pound;0.00";
	SetVisibility(this.inwardOnlineDiscount, false);
	this.inwardTicketType.innerHTML = "";

	this.totalCost.innerHTML = "&pound;0.00";
	SetVisibility(this.allPassengers, false);

	SetVisibility(this.outwardTicketInformationIcon, false);
	SetVisibility(this.inwardTicketInformationIcon, false);
};

// this is called when the select fare button is clicked
// if we have a selected fare we select the corresponding fare and service in the 
// mixing deck and minimise the lowest fare finder
LowestFareFinder.prototype.OnSelectFare = function()
{
	if (this.selectedFare)
	{
		// get the lowest fare
		var lowestFare = this.lowestFares[this.selectedFare.outwardPosition][this.selectedFare.inwardPosition];

		// and get the fare groups
		var outwardFareGroup = this.journeyPlanningData.data.outwardFareGroups[lowestFare.outwardServiceFare.fgId];
		var inwardFareGroup = null;
		
		if (lowestFare.inwardServiceFare)
		{
			inwardFareGroup = this.journeyPlanningData.data.inwardFareGroups[lowestFare.inwardServiceFare.fgId];
		}

		var outwardService = this.journeyPlanningData.outwardServiceArray[this.selectedFare.outwardPosition];
		var inwardService = this.journeyPlanningData.inwardServiceArray[this.selectedFare.inwardPosition];
	
		this.journeyPlanningData.SetSelectedServicesAndFares(outwardFareGroup, inwardFareGroup, outwardService, inwardService);
	}
	
	mixingDeck.lowestFareFinder.ShowMaximised(false); 
	mixingDeck.lowestFareFinder.ShowMinimised(true);
};
