/***********************************************************************/
/* This object handles the display of information on the service panel */
/***********************************************************************/

ServicePanel.prototype.serviceRow = 1;	// the row is a normal service 
ServicePanel.prototype.disabledServiceGroup = 2;	// the row is a line showing a group of disabled services
ServicePanel.prototype.spacingDiv = 3;	// the row is a div created so the the service panel content is high enough to scroll

// initialise the service panel
function ServicePanel(servicePanelId, direction, panelDataName, showingTimetables)
{
	this.servicePanelId = servicePanelId;
	this.panelDataName = panelDataName;
	this.direction = direction;

	// there are two templates which are copied to create the service or the line saying 'n trains unavailable for your selected fare [show]'
	this.numberOfTemplates = 2;
	this.normalRowTemplate = 0;
	this.unavailableRowTemplate = 1;

	// a service isn't selected
	this.serviceSelected = false;
	this.selectedFareGroup = null;

	this.numberOfServices = 0;
	this.spacingDivHeight = 0;
	
	this.showingTimetables = showingTimetables;
}

// add an event listenter for the selected service event
ServicePanel.prototype.AddEventListener = function(journeyPlanningData, eventHandler)
{
	if (this.changedEvent == undefined)
	{
		this.changedEvent = new YAHOO.util.CustomEvent("ServiceSelected" + this.direction);
	}

	this.changedEvent.subscribe(eventHandler, journeyPlanningData, true);
};

// fire the service selected event
ServicePanel.prototype.FireEvent = function(service)
{
	if (this.changedEvent != undefined)
	{
		this.changedEvent.fire(this.direction, service);
	}
};

// show the services in service data
ServicePanel.prototype.ShowServices = function(serviceData,  bAllServicesReceived)
{
	this.serviceData = serviceData;
	this.bAllServicesReceived = bAllServicesReceived;

	// initialise the controls
	if (this.tableContents == undefined)
	{
		this.servicePanel = document.getElementById(this.servicePanelId);
		
		this.availableTrainsTitle = YAHOO.util.Dom.getElementsByClassName("AvailableTrainsTitle", undefined, this.servicePanel)[0];
		this.tableContents = YAHOO.util.Dom.getElementsByClassName("TableContents", undefined, this.servicePanel)[0];
		this.tableHeader = YAHOO.util.Dom.getElementsByClassName("TableHeader", undefined, this.servicePanel)[0];
		this.gettingServiceData = YAHOO.util.Dom.getElementsByClassName("GettingServiceData", undefined, this.servicePanel)[0];

		this.availableTrainsCount = YAHOO.util.Dom.getElementsByClassName("AvailableTrainsCount", undefined, this.servicePanel)[0];

		YAHOO.util.Event.addListener(this.tableContents, "scroll", this.OnScroll, this, true);
		
		// make sure that we've got a reference to this panel
		this.tableContents.servicePanel = this;
	}

	// remove all the current services except for the templates
	this.Clear();

	this.AddAllServices();

	// have we got any services
	var tableRows = this.GetTableRows();
	if (tableRows.length == this.numberOfTemplates)
	{
		if (mixingDeck.journeyPlanningData.data.directServicesOnly)
		{
			// show the 'No direct services found' text
			this.availableTrainsTitle.innerHTML = mixingDeck.resourceStrings.noDirectServicesFound;
			ShowElement(this.availableTrainsCount, false);
		}
		else
		{
			// show the 'No services found' text
			this.availableTrainsTitle.innerHTML = mixingDeck.resourceStrings.noServicesFound;
			ShowElement(this.availableTrainsCount, false);
		}
	}
	else
	{
		if (this.bAllServicesReceived)
		{
			this.SetHeaderToSortable();
			this.SetAvailableTrainsCount();
		}
		else
		{
			// create the spacing div so we can always scroll
			this.CreateSpacingDiv();
		}
	}
};

// clear the services
ServicePanel.prototype.Clear = function()
{
	var tableRows = this.GetTableRows();

	for (var iRow = tableRows.length - 1; iRow >= this.numberOfTemplates; iRow--)
	{
		this.RemoveRow(tableRows[iRow]);
	}

	// and reset the table row cache
	this.UpdateTableRows();
};

// add all the services
ServicePanel.prototype.AddAllServices = function()
{    
	for (var serviceKey in this.serviceData)
	{		
		this.AddServiceData(this.serviceData[serviceKey], null);
	}

	// and reset the table row cache
	this.UpdateTableRows();
};

// add services. This is called when extension service have been retruned from the rail data provider
ServicePanel.prototype.AddServices = function(services, bPrepend, bAllServicesReceived)
{
	// clear the 'please wait' throbber
	this.ShowGettingServiceData(false);

	var previousScrollHeight = this.tableContents.scrollHeight - this.spacingDivHeight;
	
	var insertionPoint = null;

    var tableRows = this.GetTableRows();
    var found;
    for (var servicekey in services)
    {
        found = false;
        for (var row in tableRows)
	    {
	        if (tableRows[row].service)
	        {
	            if ((tableRows[row].service.dTime != services[servicekey].dTime && tableRows[row].service.dTime > services[servicekey].dTime) ||
                (tableRows[row].service.dTime == services[servicekey].dTime && tableRows[row].service.aTime > services[servicekey].aTime))
                {
	                this.AddServiceData(services[servicekey], tableRows[row]);
	                found = true;
	                break;
                }
	        }
        }   
        if(!found)
        {
            this.AddServiceData(services[servicekey], null);
        }
    }

	// remove the spacing div if we've added one
	this.RemoveSpacingDiv();

	// and reset the table row cache
	this.UpdateTableRows();

	// if we've received all the services
	if (!this.bAllServicesReceived && bAllServicesReceived)
	{
		this.bAllServicesReceived = bAllServicesReceived;
		this.SetHeaderToSortable();
		this.SetAvailableTrainsCount();
	}

	// concatanate the unavailable services
	this.HideUnavailableServices();

	// and reset the table row cache
	this.UpdateTableRows();

	// if we still have services to recieve
	if (!this.bAllServicesReceived)
	{
		// create the spacing div so we can always scroll
		this.CreateSpacingDiv();
	}

	// set the scroll position if we have a row to scroll to
	if (bPrepend)
	{
		this.disableScroll = true;

		var sizeAdded = this.tableContents.scrollHeight - previousScrollHeight;
		var scrollPosition = this.tableContents.scrollTop + sizeAdded;
		this.tableContents.scrollTop = scrollPosition;

		if (this.tableContents.scrollTop != scrollPosition)
		{
			this.tableContents.scrollTop -= 1;
		}

		this.disableScroll = false;
	}
};


// set the current scroll position
ServicePanel.prototype.SetScrollPosition = function()
{
	if (this.tableContents.scrollTop == 0)
	{
		// make sure we can scroll
		this.tableContents.scrollTop = 1;
	}
};


// add the data for a service
ServicePanel.prototype.AddServiceData = function(service, insertBefore)
{
    // increment the number of services 
	this.numberOfServices++;

	// get the normal row template
	var tableRow = this.GetTableRows()[this.normalRowTemplate];

	// copy it and insert it
	var newTableRow = tableRow.cloneNode(true);
	this.tableContents.insertBefore(newTableRow, insertBefore);

	newTableRow.rowType = this.serviceRow;

	// set the service
	newTableRow.service = service;

	// set the details
	var field = GetFirstDiv(GetFirstDiv(newTableRow));
	field = GetNextDiv(field);
	field.innerHTML = FormatTime(service.dTime);

	field = GetNextDiv(field);
	field.innerHTML = FormatTime(service.aTime);

	field = GetNextDiv(field);
	field.innerHTML = service.nChgs;

	field = GetNextDiv(field);
	field.innerHTML = FormatDuration(service.dur * 1) + "hrs";

	// get the fare panel
	field = GetNextDiv(field);

	newTableRow.webDiscount = GetFirstDiv(field);
	newTableRow.fareDiv = GetNextDiv(newTableRow.webDiscount);

	// get the div with the image
	field = GetNextDiv(field);

	// make sure it's visible
	ShowElement(newTableRow, true);

	// if we don't know the height of an item, find out now
	if (this.rowHeight == undefined)
	{
		this.rowHeight = newTableRow.scrollHeight;
		this.rowsVisible = Math.ceil(this.tableContents.clientHeight / newTableRow.scrollHeight);
	}

	// set the handlers
	// create the event args
	var eventArgs = new Object();
	eventArgs.servicePanel = this;
	eventArgs.tableRow = newTableRow;

	// and set up the events
	YAHOO.util.Event.addListener(newTableRow, "mouseover", this.MouseOver, eventArgs);
	YAHOO.util.Event.addListener(newTableRow, "mouseout", this.MouseOut, eventArgs);
	YAHOO.util.Event.addListener(newTableRow, "click", this.MouseClick, eventArgs);
	
	this.SetServiceAvailability(this.selectedFareGroup, newTableRow);
	this.SetInformationImage(newTableRow);
	this.SetTOCInformationImage(newTableRow, service);	
};

// set the TOC service icon. 
ServicePanel.prototype.SetTOCInformationImage = function(tableRow, service)
{
    // get the image element
	var imageElement = GetChildWithStyle(tableRow, "TOCInformation", true);	
	
	if (imageElement)
	{
		var backgroundImagePosition = 0;

		if (tableRow.service.warning)
		{
			backgroundImagePosition = 3;
		}
		else if (!this.IsAvailable(tableRow))
		{
			backgroundImagePosition = 2;
		}
		else if (tableRow.hover)
		{
			backgroundImagePosition = 1;
		}
		else
		{
			backgroundImagePosition = 0;
		}

		backgroundImagePosition = (backgroundImagePosition * -12) + "px 0px";

		// only set the details if they have changed
		if (imageElement.style.backgroundImage != backgroundImagePosition)
		{		    
			imageElement.style.backgroundPosition = backgroundImagePosition;
		}
        
        if (service.serviceLegs != undefined)
        {
            for (var iRow = 0; iRow < service.serviceLegs.length; iRow++)
            {   
                if(service.serviceLegs[iRow].tocImage != null && 
                    service.serviceLegs[iRow].tocImage != "" &&
                    service.serviceLegs[iRow].tocImage != undefined &&
                    service.serviceLegs[iRow].tocImage != '')
                {   
                imageElement.style.backgroundImage = "url(" + ImagePath + service.serviceLegs[iRow].tocImage + ")";
                break;
                }
            }
        }        
	}    
};

// remove a row - called when clearing the current services
ServicePanel.prototype.RemoveRow = function(tableRow)
{
	YAHOO.util.Event.removeListener(tableRow, "mouseover", this.MouseOver);
	YAHOO.util.Event.removeListener(tableRow, "mouseout", this.MouseOut);
	YAHOO.util.Event.removeListener(tableRow, "click", this.MouseClick);

	this.tableContents.removeChild(tableRow);
};

// called when the mouse is over a panel
ServicePanel.prototype.MouseOver = function(e, eventArgs)
{
	var servicePanel = eventArgs.servicePanel;
	var tableRow = eventArgs.tableRow;

	if (!tableRow.hover)
	{
		tableRow.hover = true;

		// only fire the event if we've not got a selected item in the table and this item is available
		if (tableRow.service != undefined && !servicePanel.serviceSelected && servicePanel.IsAvailable(tableRow))
		{
			servicePanel.FireEvent(tableRow.service);
		}
	}

	servicePanel.SetAppearance(tableRow);
};

// called when the mouse goes out of a service
ServicePanel.prototype.MouseOut = function(e, eventArgs)
{
	// get the details for this event. 
	// NOTE this function is not called in the context of the ServicePanel
	// therefore we must use servicePanel.blah not this.blah
	var servicePanel = eventArgs.servicePanel;
	var tableRow = eventArgs.tableRow;

	// if we're still inside this fare panel then ignore the mouse out
	if (StillInsideElementOnMouseOut(tableRow, e))
	{
		return;
	}

	tableRow.hover = false;
	
	// fire the null event if a service isn't selected and this item is available
	if (tableRow.service != undefined && !servicePanel.serviceSelected && servicePanel.IsAvailable(tableRow))
	{
		var relTarg = e.relatedTarget || e.toElement;

		// if the target has a service it will fire its own event - so we only fire our event the service is undefined
		var bFireNullEvent = (relTarg == null || relTarg == undefined);

		// if we've got a target
		if (!bFireNullEvent)
		{
			// check to see if the target is another service line. If it is then we don't have to fire
			// a null event because the target line will fire an event which will do the work for us
			var searchControl = relTarg;
			var serviceControl;
			var bChildOfThisControl;

			// look up the search tree to see if a parent is a table row
			while (searchControl != undefined)
			{
				if (HasClass(searchControl, "TableRow"))
				{
					serviceControl = searchControl;
				}
				
				if (searchControl.id == servicePanel.servicePanelId)
				{
					// there's no point looking any further
					bChildOfThisControl = true;
					break;
				}

				// move up the tree
				searchControl = searchControl.parentNode;
			}
			
			bFireNullEvent = (serviceControl == undefined || serviceControl.service == undefined || !servicePanel.IsAvailable(serviceControl) || !bChildOfThisControl);
		}
		
		// if we still have to fire the null event, fire it
		if (bFireNullEvent)
		{
			servicePanel.FireEvent(null);
		}
	}

	// set the appearance of this row
	servicePanel.SetAppearance(tableRow);
};

// called when the mouse is clicked on a service
ServicePanel.prototype.SelectService = function(service)
{
	// scroll to show the service
	var tableRow = this.ScrollToShowService(service);

	// make sure it's not already selected
	tableRow.service.selected = false;
	
	// send a mouse click for the correct service
	var eventArgs = new Object();
	eventArgs.servicePanel = this;
	eventArgs.tableRow = tableRow;
	
	this.MouseClick(undefined, eventArgs);
};

// scroll so the service after the given time is visible
ServicePanel.prototype.ScrollToShowTime = function(showTime, departAfter)
{
	// find the service that is after the departure time
	var tableRows = this.GetTableRows();

	for (var row in tableRows)
	{
		if (departAfter)
		{
			if (tableRows[row].service && tableRows[row].service.dTime > showTime)
			{
				this.ScrollToShowService(tableRows[row].service, true);
				return;
			}
		}
		else
		{
			// is it after the arrival time
			if (tableRows[row].service && tableRows[row].service.aTime > showTime)
			{
				if (row > this.numberOfTemplates)
				{
					row = row - 1;
				}
				this.ScrollToShowService(tableRows[row].service, false);
				return;
			}
		}
	}
};

// called when the mouse is clicked on a service
ServicePanel.prototype.ScrollToShowService = function(service, scrollToTop)
{
	// find the row corresponding of the service
	var tableRows = this.GetTableRows();

	for (var row in tableRows)
	{
		if (tableRows[row].service == service)
		{
			break;
		}
	}

	this.disableScroll = true;

	// get the Y position of the row
	var rowRegion = YAHOO.util.Dom.getRegion(tableRows[row]);
	var tableRegion = YAHOO.util.Dom.getRegion(tableRows[row].parentNode);
	
	if (scrollToTop)
	{
		// scroll so that the element is at the top
		var scrollPosition = tableRows[row].parentNode.scrollTop + rowRegion.top - tableRegion.top;

		tableRows[row].parentNode.scrollTop = scrollPosition;

		// if the scroll position is not what we set, then it must be at the bottom, so take one pixel off to avoid scrolling		
		if (tableRows[row].parentNode.scrollTop != scrollPosition)
		{
			tableRows[row].parentNode.scrollTop -= 1;
		}
	}
	else
	{
		if (rowRegion.bottom > tableRegion.bottom)
		{
			// scroll down
			tableRows[row].parentNode.scrollTop += rowRegion.bottom - tableRegion.bottom;
		}
		else if (rowRegion.top < tableRegion.top)
		{
			// scroll up
			tableRows[row].parentNode.scrollTop += rowRegion.top - tableRegion.top;
		}
	}

	this.disableScroll = false;

	return tableRows[row];
};

// called when the mouse is clicked on a service
ServicePanel.prototype.MouseClick = function(e, eventArgs)
{
	// NOTE this function is not called in the context of the ServicePanel
	// therefore we must use servicePanel.blah not this.blah
	var servicePanel = eventArgs.servicePanel;
	var tableRow = eventArgs.tableRow;

	// if the row isn't available, ignore this click
	if (!servicePanel.IsAvailable(tableRow))
	{
		return;
	}

	// if the row isn't a service, ignore this click
	if (tableRow.rowType != servicePanel.serviceRow)
	{
		return;
	}

	// if the row is selected
	if (tableRow.service.selected)
	{
		// clear the selection
		tableRow.service.selected = false;
		servicePanel.FireEvent(tableRow.service);
	}
	else
	{
		// clear the currently selected row if there is one
		servicePanel.ClearSelection();
		
		// and select this row
		tableRow.service.selected = true;
		servicePanel.FireEvent(tableRow.service);
	}
	
	// set the appearance of the row
	servicePanel.SetAppearance(tableRow);

	// and remember the selected service
	servicePanel.serviceSelected = tableRow.service.selected;
};

// called when the service panel scrolls
ServicePanel.prototype.OnScroll = function(e, servicePanel)
{
	// if we've disabled the scroll handler, do nothing
	if (this.disableScroll)
	{
		return;
	}

	// make sure the stopping point isn't shown
	mixingDeck.stoppingPointPanel.HidePanel();

	//get the scroll position
	var iScrollPosition = this.GetCurrentScrollPosition();
	
	// if we're at the top, get the earlier services
	if (iScrollPosition < 0)
	{
		// get earlier data
		if (mixingDeck.StartFTAEnquiryExtension((this.direction == outward) ? "O" : "R", "E"))
		{
			this.ShowGettingServiceData(true);
		}

	}	
	else if (iScrollPosition > 0) // if we're at the bottom, get the later services
	{
		// get later data
		if (mixingDeck.StartFTAEnquiryExtension((this.direction == outward) ? "O" : "R", "L"))
		{
			this.ShowGettingServiceData(true);
		}
	}
};

// get the current scroll position
ServicePanel.prototype.GetCurrentScrollPosition = function()
{
	// get the position of the items
	var tableRows = this.GetTableRows();

	// get the last visible row
	var iLastVisible = tableRows.length - 1; 
	while (iLastVisible >= 0 && tableRows[iLastVisible].style.display == 'none')
	{
		iLastVisible--;
	}

	var tableRegion = YAHOO.util.Dom.getRegion(this.tableContents);
	var topRegion = YAHOO.util.Dom.getRegion(tableRows[this.numberOfTemplates]);
	var bottomRegion = YAHOO.util.Dom.getRegion(tableRows[iLastVisible]);
	
	// do we need to scroll
	if (topRegion.top >= tableRegion.top)
	{
		return -1;
	}
	
	if (bottomRegion.bottom <= tableRegion.bottom)
	{
		return 1;
	}
	
	return 0;
};

// clear the current selection
ServicePanel.prototype.ClearSelection = function()
{
	// if there aren't any services selected, do nothing
	if (!this.serviceSelected)
	{
		return;
	}

	// get the table rows
	var tableRows = this.GetTableRows();

	// find the first selecged item
	for (var iRow = this.numberOfTemplates; iRow < tableRows.length; iRow++)
	{
		if (tableRows[iRow].service && tableRows[iRow].service.selected)
		{
			// and clear it's appearance
			tableRows[iRow].service.selected = false;
			this.SetAppearance(tableRows[iRow]);
			break;
		}
	}

	this.serviceSelected = false;
};


// set the services available for the given fareGroup
ServicePanel.prototype.SetAvailableServices = function(fareGroup)
{
	// remember the current fare group for adding new services
	this.selectedFareGroup = fareGroup;

	// remove the spacing div if there is one
	this.RemoveSpacingDiv();

	// remove any 'x unavailable trains' rows
	this.RemoveAllDisabledServiceGroupRows();

	this.EnableAllRows();

	// get the table rows
	var tableRows = this.GetTableRows();

	// for each service
	for (var iRow = this.numberOfTemplates; iRow < tableRows.length; iRow++)
	{
		// set the availability
		this.SetServiceAvailability(fareGroup, tableRows[iRow]);
	}	

	// concatanate the unavailable services
	this.HideUnavailableServices();

	// if we still have services to recieve
	if (!this.bAllServicesReceived)
	{
		// make sure the window is big enough to scroll
		this.CreateSpacingDiv();
	}
	else
	{
		// sort the data by the required column
		this.SortByColumn(this.sortColumn);
	}

	// set the scroll position so that we're not at the top
	this.SetScrollPosition();
};

// set the availability for a service
ServicePanel.prototype.SetServiceAvailability = function(fareGroup, tableRow)
{
	if (fareGroup == null)
	{
		// show all services
		this.SetServicePrice(tableRow);
	}
	else
	{
		// set the details for the selected fare
		// get the service
		var service = tableRow.service;
	
		// if it's available
		if (service._available)
		{
			// show the price for the fare
			if (this.direction == inward && fareGroup.isReturn)
			{
				// it's a return, so don't show the price
				this.SetServicePrice(tableRow);
			}
			else
			{
				// show the price
				this.SetServicePrice(tableRow, service._serviceFare, service.isDiscounted);
			}
		}
		else
		{
			// this fare is not available
			this.SetServicePrice(tableRow);
					
			// if it's selected
			if (tableRow.service.selected && fareGroup.selected)
			{
				// deselect it
				tableRow.service.selected = false;
				
				// and fire the event to let others know about it
				this.FireEvent(null);
			}
		}
	}

	// set the appearance of the row
	this.SetAppearance(tableRow);
};

// set the appearance of a service
ServicePanel.prototype.SetAppearance = function(tableRow)
{
	var title = "";

	var tableRowClassName = "";
	var tableRowContentsClassName = "";

	// handle the 'n trains unavailable' row
	if (tableRow.rowType != this.serviceRow)
	{
		// it's not a service row, it's an 'n trains unavailable' row
		if (tableRow.hover)
		{
			// hovered over
			tableRowClassName = "Hover";
		}
	}
	else if (!this.IsAvailable(tableRow))
	{
		tableRowContentsClassName = "RowNotAvailable";
	}
	else
	{
		// it's a normal service which is available
		if (tableRow.hover)
		{
			// hovered over
			if (tableRow.service.selected)
			{
				// and selected
				tableRowContentsClassName = "RowHoverSelected";
			}
			else
			{
				// just hovered over
				tableRowContentsClassName = "RowHover";
			}
		}
		else
		{
			// selected but not hovererd over
			if (tableRow.service.selected)
			{
				tableRowContentsClassName = "RowSelected";
			}
		}
		// set the information icon
		this.SetInformationImage(tableRow);
	}
	
	if (mixingDeck.disablesleeperservices == 1)
	{
	    if (this.IsSleeperService(tableRow))
	    {
	        // it's a service row but it's not available
			tableRowContentsClassName = "RowNotAvailable";
		    title = mixingDeck.resourceStrings.serviceCannotBeSelected;
		    this.SetInformationImage(tableRow);
	    }
	}

	// set the details	
	var tableRowContent = GetFirstDiv(tableRow);

	// only update the details if they have changed
	if (tableRowContent.title != title)
	{
		tableRowContent.title = title;
	}

	tableRow.className = "TableRow " + tableRowClassName;
	tableRowContent.className = "TableRowContents " + tableRowContentsClassName;
};

// set the information icon. If the service has a warning flag, show the warning icon
// the icon is selected by changing the background position of the image
ServicePanel.prototype.SetInformationImage = function(tableRow)
{
	// get the image element
	var imageElement = GetChildWithStyle(tableRow, "ServiceInformation", true);
	
	if (imageElement)
	{
		var backgroundImagePosition = 0;

		if (tableRow.service.warning)
		{
			backgroundImagePosition = 3;
		}
		else if (!this.IsAvailable(tableRow))
		{
			backgroundImagePosition = 2;
		}
		else if (tableRow.hover)
		{
			backgroundImagePosition = 1;
		}
		else
		{
			backgroundImagePosition = 0;
		}

		backgroundImagePosition = (backgroundImagePosition * -12) + "px 0px";

		// only set the details if they have changed
		if (imageElement.style.backgroundImage != backgroundImagePosition)
		{
			imageElement.style.backgroundPosition = backgroundImagePosition;
		}
	}
};


// get the rows of the panel
ServicePanel.prototype.GetTableRows = function(reset)
{
	// can we get it from the cached version
	if (this.tableRows == undefined || reset)
	{
		// no, so get the details
		this.UpdateTableRows();
	}
	return this.tableRows;
};

// get the rows shown on the panel
ServicePanel.prototype.UpdateTableRows = function(reset)
{
	if (this.tableRows == undefined)
	{	
		this.tableRows = YAHOO.util.Dom.getElementsByClassName("TableRow", undefined, this.tableContents);
	}
	else
	{
		// set the first element
		this.tableRows[0] = GetFirstDiv(this.tableContents);

		// set all it's siblings
		var iRow = 1;
		var thisRow = GetNextDiv(this.tableRows[0]);
		
		while (thisRow != undefined)
		{
			this.tableRows[iRow] = thisRow;
			
			thisRow = GetNextDiv(thisRow);
			iRow++;
		}
		this.tableRows.length = iRow;
	}
};

// set the price of a service
ServicePanel.prototype.SetServicePrice = function(tableRow, iPrice, isDiscounted)
{
	// set the discounted information
	if (isDiscounted)
	{
		// show the web discount
		SetVisibility(tableRow.webDiscount, true);
		YAHOO.util.Dom.addClass(tableRow.fareDiv, "DiscountedFarePrice");
	}
	else
	{
		// hide the web discount
		SetVisibility(tableRow.webDiscount, false);
		YAHOO.util.Dom.removeClass(tableRow.fareDiv, "DiscountedFarePrice");
	}

	// have we got a price
	if (iPrice == undefined)
	{
		// if we haven't, show the CRS code for timetables
		if (this.showingTimetables)
		{
			ShowElement(tableRow.webDiscount, false);
			
			var showCrsCodes;
			
			if (this.direction == outward)
			{
				showCrsCodes = (tableRow.service.oNlc != mixingDeck.journeyPlanningData.data.requestedOriginNlc || tableRow.service.dNlc != mixingDeck.journeyPlanningData.data.requestedDestinationNlc);
			}
			else
			{
				showCrsCodes = (tableRow.service.dNlc != mixingDeck.journeyPlanningData.data.requestedOriginNlc || tableRow.service.oNlc != mixingDeck.journeyPlanningData.data.requestedDestinationNlc);
			}
			
			if (showCrsCodes)
			{
				// Are the origin or destination stations group stations?
				tableRow.fareDiv.innerHTML = mixingDeck.journeyPlanningData.data.locations[tableRow.service.oNlc].crs + "&rarr;" + mixingDeck.journeyPlanningData.data.locations[tableRow.service.dNlc].crs;
			}

			tableRow.title = mixingDeck.journeyPlanningData.data.locations[tableRow.service.oNlc].desc + " to " + mixingDeck.journeyPlanningData.data.locations[tableRow.service.dNlc].desc;

			for (var toc in tableRow.service.tocs)
			{
				tableRow.title += " \nOperator " + mixingDeck.journeyPlanningData.data.tocs[tableRow.service.tocs[toc]].name;
			}
		}
		else
		{
			// otherwide, for the mixing deck, show a blank price
			tableRow.fareDiv.innerHTML = "";
		}
		tableRow.price = undefined;

	}
	else
	{
		// format the proce and show it
		tableRow.fareDiv.innerHTML = FormatPrice(iPrice);
		tableRow.price = iPrice;
	}
};

// is this panel showing the 'please wait' panel
ServicePanel.prototype.IsShowingWaitPanel = function(show)
{
	return this.gettingServiceData.style.display == "block";
};

// show or hide the 'please wait' item
ServicePanel.prototype.ShowGettingServiceData = function(show)
{
	ShowElement(this.gettingServiceData, show);
};

// hide services that are not available
ServicePanel.prototype.HideUnavailableServices = function()
{
	var tableRows = this.GetTableRows();

	// go through all the 
	var iDisabledCount = 0;
	var disabledServiceGroupItems = 0;
	var disabledServiceGroupClosed = true;
	
	// for each row, moving backwards throught the list
	for (var iRow = tableRows.length - 1; iRow >= this.numberOfTemplates - 1; iRow--)
	{
		// is this unavailable
		if (tableRows[iRow].rowType == this.serviceRow && !this.IsAvailable(tableRows[iRow]))
		{
			// a disabled row so just count it for now
			iDisabledCount++;
		}
		else if (tableRows[iRow].rowType == this.disabledServiceGroup)
		{
			// it's another disabled service group so remember the number of items
			disabledServiceGroupItems = tableRows[iRow].serviceCount;
			disabledServiceGroupClosed = tableRows[iRow].isClosed;
		}
		else 
		{
			// if there are more than 1 disabled items
			if (iDisabledCount > 1)
			{
				// if the number of disabed items isn't the same as the current count
				if (disabledServiceGroupItems != iDisabledCount)
				{
					// we're at the row before the first row to be disabled
					// so move to the next row
					var iRowToDisable = iRow + 1;

					// remove all disabledServiceGroups and set the serviceRows to the correct state
					while (tableRows[iRowToDisable] != undefined)
					{
						// is this a service row?
						if (tableRows[iRowToDisable].rowType == this.serviceRow)
						{
							// if it's not available
							if (this.IsAvailable(tableRows[iRowToDisable]))
							{
								// we've reached a row which shouldn't be disabled, so we're reached the end of the rows to be collected together
								// so stop doing stuff 
								break;
							}
							else
							{
								// set the visibility of the item to be hidden if the group is closed
								ShowElement(tableRows[iRowToDisable], !disabledServiceGroupClosed);
							}
						}
						else if (tableRows[iRowToDisable].rowType == this.disabledServiceGroup)
						{
							// it's a disabled service group and as as it's alread part of another disabled service group, we don't
							// need this row, so remove it
							this.RemoveDisabledServiceGroupRow(tableRows[iRowToDisable]);
						}

						// move to the next row						
						iRowToDisable++;
					}
				
					// add a new row with iDisabledCount items after this row
					this.AddServiceUnavailableRow(iDisabledCount, GetNextDiv(tableRows[iRow]), disabledServiceGroupClosed);
				}
			}
			// start again and look for the next series of disabled rows
			iDisabledCount = 0;
			disabledServiceGroupItems = 0;
		}
	}

	// update the array of rows
	this.UpdateTableRows();
};

// remove all disabled service groups
ServicePanel.prototype.RemoveAllDisabledServiceGroupRows = function()
{
	var tableRows = this.GetTableRows();

	// remove all the lines indicating suppressed serices
	for (var iRow = this.numberOfTemplates; iRow < tableRows.length; iRow++)
	{
		if (tableRows[iRow].rowType == this.disabledServiceGroup)
		{
			this.RemoveDisabledServiceGroupRow(tableRows[iRow]);
		}
	}

	// update the array of rows
	this.UpdateTableRows();
};

// remove a disabled service group row
ServicePanel.prototype.RemoveDisabledServiceGroupRow = function(tableRow)
{
	if (tableRow.rowType == this.disabledServiceGroup)
	{
		// and set up the events
		YAHOO.util.Event.removeListener(tableRow, "mouseover", this.MouseOver);
		YAHOO.util.Event.removeListener(tableRow, "mouseout", this.MouseOut);

		this.tableContents.removeChild(tableRow);

		// update the array of rows
		this.UpdateTableRows();
	}
};

// enable all the rows
ServicePanel.prototype.EnableAllRows = function()
{
	var tableRows = this.GetTableRows();

	// remove all the lines indicating suppressed serices
	for (var iRow = this.numberOfTemplates; iRow < tableRows.length; iRow++)
	{
		// make sure it's visible
		ShowElement(tableRows[iRow], true);
	}
};

// add a 'n services unavailable' row
ServicePanel.prototype.AddServiceUnavailableRow = function(serviceCount, insertBefore, isClosed)
{
	// get the normal row template
	var tableRow = this.GetTableRows()[this.unavailableRowTemplate];

	// copy it and insert it
	var newTableRow = tableRow.cloneNode(true);
	this.tableContents.insertBefore(newTableRow, insertBefore);

	newTableRow.rowType = this.disabledServiceGroup;
	newTableRow.serviceCount = serviceCount;
	newTableRow.isClosed = isClosed;

	if (!isClosed)
	{
		// get the button
		var button = YAHOO.util.Dom.getElementsByClassName("nisservices", "A", newTableRow)[0];

		SetWTButtonText(button, mixingDeck.resourceStrings.hide);
	}

	var rowText = GetFirstDiv(GetFirstDiv(newTableRow));

	// set the text
	rowText.innerHTML = serviceCount + ' ' + rowText.innerHTML;

	// make sure it's visible
	ShowElement(newTableRow, true);

	// create the event args
	var eventArgs = new Object();
	eventArgs.servicePanel = this;
	eventArgs.tableRow = newTableRow;

	// and set up the events
	YAHOO.util.Event.addListener(newTableRow, "mouseover", this.MouseOver, eventArgs);
	YAHOO.util.Event.addListener(newTableRow, "mouseout", this.MouseOut, eventArgs);
};

// called when the 'show' button on a disbled service group row is clicked
ServicePanel.prototype.OnShowServiceClick = function(button)
{
	var tableRow = GetParentWithStyle(button, "TableRow");

	var servicePanel = GetParentWithStyle(button, "TableContents").servicePanel;
	
	servicePanel.RemoveSpacingDiv();
	
	if (tableRow.isClosed)
	{
		tableRow.isClosed = false;
		
		// show all the unavailable services
		tableRow = GetNextDiv(tableRow);
		while (tableRow != undefined && tableRow.style.display == 'none')
		{
			tableRow.style.display = 'block';
			tableRow = GetNextDiv(tableRow);
		}

		// change the button text to 'hide'
		SetWTButtonText(button, mixingDeck.resourceStrings.hide);
	}
	else
	{
		tableRow.isClosed = true;

		// hide all the available services
		tableRow = GetNextDiv(tableRow);
		while (tableRow != undefined && !this.IsAvailable(tableRow))
		{
			tableRow.style.display = 'none';
			tableRow = GetNextDiv(tableRow);
		}

		// change the button test to 'show'
		SetWTButtonText(button, mixingDeck.resourceStrings.show);
	}

	// update the spacing div and scroll position
	servicePanel.CreateSpacingDiv();
	servicePanel.SetScrollPosition();
};

// remove the spacing div
ServicePanel.prototype.RemoveSpacingDiv = function()
{
	if (this.spacingDivHeight > 0)
	{
		this.spacingDivHeight = 0;

		var tableRows = this.GetTableRows();

		// if the last row is a spacing div
		for (var iRow = this.numberOfTemplates; iRow < tableRows.length; iRow++)
		{
			if (tableRows[iRow].rowType == this.spacingDiv)
			{
				// then remove it
				this.tableContents.removeChild(tableRows[iRow]);
				this.GetTableRows(true);
				break;
			}
		}
	}
};

// add a spacing div. This is used to ensure the height of the contents is big enough to be scrollable. 
// We need to be able to scroll to get previous and next services
ServicePanel.prototype.CreateSpacingDiv = function()
{
	// don't create a spacing div if all the services have been reveived. There is no need to ensure the scroll bar is available as
	// scrolling to the bottom and top won't get any more trains (we've got them all!)
	if (this.bAllServicesReceived)
	{
		return;
	}

	var tableRegion = YAHOO.util.Dom.getRegion(this.tableContents);

	// find the last visible line
	var tableRows = this.GetTableRows();
	for (var iRow = tableRows.length - 1; iRow >= 0; iRow--)
	{
		if (tableRows[iRow].style.display != "none")
		{
			break;
		}
	}
	
	// get the region of the last visible line
	var rowRegion = YAHOO.util.Dom.getRegion(tableRows[iRow]);

	this.spacingDivHeight = tableRegion.bottom + 2 - rowRegion.bottom - this.tableContents.scrollTop;

	if (this.spacingDivHeight > 0)
	{
		// create a dummy div with the correct height to ensure scrolling
		var newSpacingDiv = document.createElement('div');

		newSpacingDiv.rowType = this.spacingDiv;
		newSpacingDiv.style.height = (this.spacingDivHeight) + "px";

		this.tableContents.appendChild(newSpacingDiv);
		this.tableRows[this.tableRows.length] = newSpacingDiv;
	}
	else
	{
		this.spacingDivHeight = 0;
	}
};

// the information icon has been clicked
ServicePanel.prototype.OnInformationClick = function(e, icon)
{
	var tableRow = GetParentWithStyle(icon, "TableRow");
	var servicePanel = GetParentWithStyle(icon, "TableContents").servicePanel;

	// get the position of the icon
	var iconRegion = YAHOO.util.Dom.getRegion(icon);

	// if it's the timetable page, display the service popup in a different place
	if (servicePanel.showingTimetables)
	{
		mixingDeck.ShowServiceInformation(tableRow.service, iconRegion.right + 380, (iconRegion.top + iconRegion.bottom) / 2 + 205);
	}
	else
	{
		mixingDeck.ShowServiceInformation(tableRow.service, (iconRegion.left + iconRegion.right) / 2 + 58, iconRegion.top - 12);
	}

	// don't bubble up as this will cause the line to be selected and the panel to disappear
	e.cancelBubble = true;
};


// the headers are now sortable, so changed the appearance
// The sorting functionality has now been removed so these functions are not called
ServicePanel.prototype.SetHeaderToSortable = function()
{
//	// set the header to show that the items are sortable
//	YAHOO.util.Dom.addClass(this.tableHeader, "Sortable");

//	var sortedHeader = GetFirstDiv(this.tableHeader);
//	YAHOO.util.Event.addListener(sortedHeader, "click", this.SortByDeparture, this, true);

//	sortedHeader = GetNextDiv(sortedHeader);
//	YAHOO.util.Event.addListener(sortedHeader, "click", this.SortByArrive, this, true);

//	sortedHeader = GetNextDiv(sortedHeader);
//	YAHOO.util.Event.addListener(sortedHeader, "click", this.SortByChanges, this, true);

//	sortedHeader = GetNextDiv(sortedHeader);
//	YAHOO.util.Event.addListener(sortedHeader, "click", this.SortByDuration, this, true);

//	sortedHeader = GetNextDiv(sortedHeader);
//	YAHOO.util.Event.addListener(sortedHeader, "click", this.SortByPrice, this, true);

//	this.SetSortColumn(0);
};

////////////////////////////////////////////////////////////////////
// start of obsolete sorting functions
////////////////////////////////////////////////////////////////////

// various sort functions
ServicePanel.prototype.SortByDeparture = function()
{
	this.SetSortColumn(0);
	this.SortByColumn(0);
};

ServicePanel.prototype.SortByArrive = function()
{
	this.SetSortColumn(1);
	this.SortByColumn(1);
};

ServicePanel.prototype.SortByChanges = function()
{
	this.SetSortColumn(2);
	this.SortByColumn(2);
};

ServicePanel.prototype.SortByDuration = function()
{
	this.SetSortColumn(3);
	this.SortByColumn(3);
};

ServicePanel.prototype.SortByPrice = function()
{
	this.SetSortColumn(4);
	this.SortByColumn(4);
};

// sort by a specific column
ServicePanel.prototype.SortByColumn = function(column)
{
	switch (column)
	{
		case 0:
			this.Sort(this.CompareByDepartureTime);
			break;

		case 1:
			this.Sort(this.CompareByArrivalTime);
			break;

		case 2:
			this.Sort(this.CompareByChanges);
			break;

		case 3:
			this.Sort(this.CompareByDuration);
			break;

		case 4:
			this.Sort(this.CompareByPrice);
			break;
	}
};

// set the appearance of a sorted column header
ServicePanel.prototype.SetSortColumn = function(column)
{
	var iColumn;

	// set the background of the sorted column
	if (this.sortColumn != undefined)
	{
		var sortedHeader = GetFirstDiv(this.tableHeader);
	
		// remove the sorting of the current column
		for (iColumn = 0; iColumn < this.sortColumn; iColumn++)
		{
			sortedHeader = GetNextDiv(sortedHeader);
		}

		YAHOO.util.Dom.removeClass(sortedHeader, "Sorted");
		YAHOO.util.Dom.removeClass(GetNextDiv(sortedHeader), "SortedBorder");
	}

	this.sortColumn = column;

	// and add the styles to sort this column
	sortedHeader = GetFirstDiv(this.tableHeader);

	// remove the sorting of the current column
	for (iColumn = 0; iColumn < this.sortColumn; iColumn++)
	{
		sortedHeader = GetNextDiv(sortedHeader);
	}

	YAHOO.util.Dom.addClass(sortedHeader, "Sorted");
	YAHOO.util.Dom.addClass(GetNextDiv(sortedHeader), "SortedBorder");
};

// sort the rows
ServicePanel.prototype.Sort = function(sortBy)
{
	var tableRows = this.GetTableRows();

	// remember the first item in the array as the first item to delete
	var rowToDelete = tableRows[this.numberOfTemplates];

	// remove any non-serviceRow from the array
	for (var iRow = tableRows.length - 1; iRow >= 0; iRow--)
	{
		if (tableRows[iRow].rowType != this.serviceRow)
		{
			// remove this item
			tableRows.splice(iRow, 1);
		}
	}
	
	// sort the array by the sortBy function
	tableRows.sort(sortBy);

	// remove all the items from the table 
	while (rowToDelete != undefined)
	{
		var nextRowToDelete = rowToDelete.nextSibling;
		this.tableContents.removeChild(rowToDelete);
		rowToDelete = nextRowToDelete;
	}
	
	// re-insert in the sorted order
	for (iRow = 0; iRow < tableRows.length; iRow++)
	{
		this.tableContents.appendChild(tableRows[iRow]);
	}
	
	// reset the table rows array
	this.GetTableRows(true);
	
	// put in the disabledServiceGroups
	this.HideUnavailableServices();
};

// comparison functions
ServicePanel.prototype.CompareByArrivalTime = function(a, b)
{
	if (a.service.aTime > b.service.aTime) return 1;
	else if (a.service.aTime < b.service.aTime) return -1;
	// they're equal, so compare by departure time
	else if (a.service.dTime > b.service.dTime) return 1;
	else if (a.service.dTime < b.service.dTime) return -1;
	else return 0;
};

ServicePanel.prototype.CompareByDepartureTime = function(a, b)
{
	if (a.service.dTime > b.service.dTime) return 1;
	else if (a.service.dTime < b.service.dTime) return -1;
	// they're equal, so compare by arrival time
	else if (a.service.aTime > b.service.aTime) return 1;
	else if (a.service.aTime < b.service.aTime) return -1;
	else return 0;
};

ServicePanel.prototype.CompareByChanges = function(a, b)
{
	if (a.service.nChgs > b.service.nChgs) return 1;
	else if (a.service.nChgs < b.service.nChgs) return -1;
	else return 0;
};

ServicePanel.prototype.CompareByDuration = function(a, b)
{
	var aDuration = a.service.aTime - a.service.dTime;
	var bDuration = b.service.aTime - b.service.dTime;

	if (aDuration > bDuration) return 1;
	else if (aDuration < bDuration) return -1;
	else return 0;
};

ServicePanel.prototype.CompareByPrice = function(a, b)
{
	if (a.price > b.price) return 1;
	else if (a.price < b.price) return -1;
	else return 0;
};

////////////////////////////////////////////////////////////////////
// end of obsolete sorting functions
////////////////////////////////////////////////////////////////////


// set the counts for the 'showing n of m' line
ServicePanel.prototype.SetAvailableTrainsCount = function()
{
	// remember the text
	if (this.originalTrainCountText == undefined)
	{
		this.originalTrainCountText = this.availableTrainsCount.innerHTML;
	}
	
	var strDisplay = this.originalTrainCountText;
		
	strDisplay = strDisplay.replace("[showing]", Math.min(this.rowsVisible, this.numberOfServices));
	strDisplay = strDisplay.replace("[total]", this.numberOfServices);

	this.availableTrainsCount.innerHTML = strDisplay;

	// set the visibility
	SetVisibility(this.availableTrainsCount, true);
};

var useNewService = true;

ServicePanel.prototype.IsAvailable = function(tableRow)
{
	if (useNewService)
	{
		return tableRow.service._available;
	}
	else
	{
		return tableRow.available;
	}
};

ServicePanel.prototype.IsSleeperService = function(tableRow)
{
    if(tableRow.service != undefined)
    {
        if (tableRow.service.serviceLegs != undefined)
        {
            for (var serviceLegsId in tableRow.service.serviceLegs)
            {
                switch (tableRow.service.serviceLegs[serviceLegsId].bClass)
	            {
		            case 'S':
		            case 'B':
		            case 'F':
		            {
			            return true;
		            }
		            break;
		            default: return false;
	            }
            }
        }
        else
        {
            return false;
        }
    }
    else
    {
        return false;
    }
};

