var HotelSearchResults = (function() {

  // ============================================================================================================
  // Initialisation
  // ============================================================================================================
  function init() {
    showMinRows();
    handleSummaryGridPopup();
    handleReducedDescriptions();
    hookReSearchEvents();
    hookTransferButtons();
  }
  
  // ============================================================================================================
  // Show Min/Full Available Rate Data
  // ============================================================================================================
  function showMinRows(strHotelId, strBoardBasisLeaveVisible) {
    if (typeof(strHotelId) == "undefined") {
      strHotelId = null;
    }
    if (typeof(strBoardBasisLeaveVisible) == "undefined") {
      strBoardBasisLeaveVisible = null;
    }

    $("div.HotelSearchResults div.HotelSearchResultsDetails table.Rooms").each(function(){
      var objBoardBasisPresent = [];
      if ((strHotelId == null) || (this.className === strHotelId)) {
        var $table = $(this);
        var bRowsHidden = false;
        var strHotelIdCurrent = getHotelIdFromTableClassName(this.className);
        $table.find("tr").each(function(){
          var strBoardBasis = getBoardBasisFromRowClassName(this.className);
          if ((strBoardBasisLeaveVisible == null) || (strBoardBasis != strBoardBasisLeaveVisible)) {
            if (strBoardBasis != null) {
              if (objBoardBasisPresent[strBoardBasis]) {
                bRowsHidden = true;
              }
              else {
                objBoardBasisPresent[strBoardBasis] = true;
                $(this).addClass("MinRate");
              }
            }
          }
        });
        if (bRowsHidden) {
          var objEleLinkAll = document.createElement("a");
          objEleLinkAll.href = "#";
          objEleLinkAll.className = "ViewAllRates";
          objEleLinkAll.innerHTML = "View all available rates for this Hotel";
          $(objEleLinkAll).click(function(){
            removeTransferRows();
            var $cont = $(this).closest("div.ResultEntry");
            $cont.find("table.Rooms tr").show();
            $cont.find("a.ViewMinRates").show();
            $(this).hide();
            return false;
          });
          
          var objEleLinkMin = document.createElement("a");
          objEleLinkMin.href = "#";
          objEleLinkMin.className = "ViewMinRates";
          objEleLinkMin.innerHTML = "View minimum rates only for this Hotel";
          objEleLinkMin.style.display = "none";
          $(objEleLinkMin).click(function(){
            removeTransferRows();
            var $cont = $(this).closest("div.ResultEntry");
            $cont.find("table.Rooms tr").not(".Header, .MinRate").hide();
            $cont.find("a.ViewAllRates").show();
            $(this).hide();
            return false;
          });

          var objEleLinkCont = document.createElement("div");
          objEleLinkCont.className = "RateViewControls"
          objEleLinkCont.appendChild(objEleLinkAll);
          objEleLinkCont.appendChild(objEleLinkMin);
          $table[0].parentNode.appendChild(objEleLinkCont);
          
          $table.find("tr").not(".Header, .MinRate").hide();
        }
      }
    });

    function getBoardBasisFromRowClassName(strClassName) {
      if (strClassName) {
        var arrClass = ("" + strClassName).split(" ");
        for (var intIndex = 0; intIndex < arrClass.length; intIndex++) {
          if (arrClass[intIndex].substring(0, 11) == "BoardBasis:") {
            return arrClass[intIndex].substring(11, arrClass[intIndex].length);
            break;
          }
        }
      }
      return null;
    }
  }

  // ============================================================================================================
  // Best Price Summary Grid Popup
  // ============================================================================================================
  var bBestPricePopupShouldBeVisible = false;
  var objElePopupContainer = null;
  function handleSummaryGridPopup() {
    $("div.HotelSearchResultsSummary table td span").each(function() {
      var strHotelId = getHotelIdFromTableClassName(this.className);
      if (strHotelId) {
        var objBestPriceData = arrBestPriceData[strHotelId];
        if (objBestPriceData) {

          // Create PopupContainer if not already done (hook Hover event to keep it visible
          // while mousing over and allow to disappear when not)
          if (!objElePopupContainer) {
            objElePopupContainer = getPopupContainer($(this).closest("div")[0]);
            $(objElePopupContainer).hover(
              function() {
                bBestPricePopupShouldBeVisible = true;
              },
              function() {
                markPopupContainerForHiding(1000);
              }
            );
          }

          $(this).hover(
            function() {
              bBestPricePopupShouldBeVisible = true;
              objElePopupContainer.innerHTML = getPopupContent(objBestPriceData);
              
              var arrClickEvents = [
                { "Target": "a.ViewSimilar", "Action": function() { researchSimilar(objBestPriceData.StarRating, objBestPriceData.BoardBasisCode); } },
                { "Target": "a.ViewRateDetails", "Action": function() { researchSpecificHotel(objBestPriceData.Id); } }
              ];
              
              // This waits a short delay before trying to access the click event of the links in case since we have
              // just created them using the innerHTML property - sometimes the browser needs a moment to finish
              // creating the elements before we can access them
              for (var intIndex = 0; intIndex < arrClickEvents.length; intIndex++) {
                bindClickEvent(arrClickEvents[intIndex].Target, arrClickEvents[intIndex].Action);
              }
              
              setTimeout(function(){
                $(objElePopupContainer).show();
              }, 1000);

              function bindClickEvent(strPath, fncClick) {
                setTimeout(function() {
                  // Use unbind to ensure we don't end up with duplicate handlers
                  var $lnk = $(objElePopupContainer).find(strPath);
                  if ($lnk.length > 0) {
                    $lnk.unbind("click");
                    $lnk.click(function(){
                      fncClick();
                      return false;
                    });
                  }
                }, 10);
              }
            },
            function() {
              markPopupContainerForHiding(2000);
            }
          );
        }
      }
    });
    
    function markPopupContainerForHiding() {
      bBestPricePopupShouldBeVisible = false;
      setTimeout(function(){
        if (objElePopupContainer) {
          if (!bBestPricePopupShouldBeVisible) {
            $(objElePopupContainer).hide();
          }
        }
      }, 1000);
    }
    
    function getPopupContainer(objEleParent) {
      // Note: Create it initially hidden
      var $cont = $(objEleParent).find("div.PopupContainer");
      if ($cont.length > 0) {
        return $cont[0];
      }
      var objEleContNew = document.createElement("div");
      objEleContNew.className = "PopupContainer";
      objEleContNew.style.display = "none";
      objEleParent.appendChild(objEleContNew);
      return objEleContNew;
    }
    
    function getPopupContent(objBestPriceData) {
      var imgTag;
      if (objBestPriceData.ImagePath) {
        imgTag = "<p class=\"Image\"><img src=\"" + objBestPriceData.ImagePath + "\" /></p>";
      }
      else {
        imgTag = "";
      }
      var hotelDetailUrl = getHotelDetailPopUpUrl(objBestPriceData.HotelId);
      return [
        "<div class=\"PopupContent BrandBackgroundColour\">",
        imgTag,
        "<h2>" + objBestPriceData.Name + " - cheapest " + objBestPriceData.StarRating + " star (" + objBestPriceData.BoardBasisCode + ")</h2>",
        "<p class=\"Cost\">" + objBestPriceData.BestPrice + "</p>",
        "<p class=\"Desc\">" + objBestPriceData.Description + "</p>",
        "<p><a class=\"ViewHotelDetails\" href=\"" + hotelDetailUrl + "\" onclick=\"popUpURL(this.href, 'HotelInformation', 5);return false;\">Detailed Accommodation Information</a></p>",
        "<p><a class=\"ViewRateDetails\" href=\"#\" onclick=\"return false;\">View Rate Details / Book</a></p>",
        "<p><a class=\"ViewSimilar\" href=\"#\" onclick=\"return false;\">Show all " + objBestPriceData.StarRating + " star (" + objBestPriceData.BoardBasisCode + ") hotels</a></p>",
        "</div>"
      ].join("");
    }
    
    function researchSimilar(intStarRating, strBoardBasisCode) {
      reSearch({
        "category": "[" + intStarRating + "]",
        "boardbasis": strBoardBasisCode,
        "theText": "",
        "FindInSearch": "",
        "MaxPrice": "",
        "pageNo": 1
      });
    }

    function researchSpecificHotel(strSuperHotelId) {
      reSearch({
        "theText": strSuperHotelId,
        "category": "",
        "boardbasis": "",
        "FindInSearch": "",
        "MaxPrice": "",
        "pageNo": 1
      });
    }
    
    function getHotelDetailPopUpUrl(strHotelId) {
      var dtStart = objSearchCriteria.StartDate;
      var dtEnd = dateAddDays(objSearchCriteria.StartDate, objSearchCriteria.NumNights);
      return "hotel_details.asp?hotelid=" + strHotelId + "&er=1&startdate=" + formatDate(dtStart) + "&enddate=" + formatDate(dtEnd);
    }
  }
  
  // ============================================================================================================
  // Trim long descriptions (append "Read More" button to view full content)
  // ============================================================================================================
  var bFullDescPopupShouldBeVisible_Link = false;
  var bFullDescPopupShouldBeVisible_Popup = false;
  function handleReducedDescriptions() {
    // Trim long descriptions - create a "Read More" button to reveal full content. Trim descriptions down to
    // 400 characters if they are longer than 450 (this 50 characters difference is to ensure we don't hide
    // just a few words)
    $("div.HotelSearchResults div.HotelSearchResultsDetails p.Description").each(function(){
      var strValue = ("" + this.innerHTML);
      if (strValue.length > 450) {
        
        // Get ResultHeader container we'll insert the element into
        var objCont = $(this).closest("div.ResultHeader");
        if (objCont.length === 0) {
          alert("Unable to locate ResultHeader container");
          return;
        }
      
      
        var intBreak = strValue.indexOf(" ", 400);
        this.innerHTML = strValue.substring(0, intBreak) + ".. ";;
        
        var objEleReadMore = document.createElement("a");
        objEleReadMore.href = "#";
        objEleReadMore.innerHTML = "(read more)";
        this.appendChild(objEleReadMore);
        $(objEleReadMore).click(function(){
          return false;
        });
        
        var objEleDescCont = document.createElement("div");
        objEleDescCont.className = "DescriptionPopup";
        objEleDescCont.innerHTML = "<div class=\"Content\">" + strValue + "</div>"
        $(objEleDescCont).hide();
        objCont[0].appendChild(objEleDescCont);
        
        // Set up hover functionality on link (leave 10ms to ensure element is fully initialised so that
        // we can hook these events)
        setTimeout(function(){
          hookReadMoreLinkHover(objEleReadMore, objEleDescCont);
        }, 10);

        // Set up hover functionality on the popup (so it doesn't disappear while the cursor is on it)
        // we can hook these events)
        setTimeout(function(){
          hookFullDescPopupHover(objEleDescCont);
        }, 10);
      }
    });
    
    function hookReadMoreLinkHover(objEleReadMore, objEleDescCont) {
      $(objEleReadMore).hover(
        function() {
          bFullDescPopupShouldBeVisible_Link = true;
          $("div.HotelSearchResults div.HotelSearchResultsDetails div.DescriptionPopup").not(objEleDescCont).hide();
          $(objEleDescCont).show();
        },
        function() {
          bFullDescPopupShouldBeVisible_Link = false;
          setTimeout(function(){
            if (!bFullDescPopupShouldBeVisible_Popup && !bFullDescPopupShouldBeVisible_Link) {
              $(objEleDescCont).hide();
            }
          }, 1000);
        }
      );
    }
    
    function hookFullDescPopupHover(objEleDescCont) {
      $(objEleDescCont).hover(
        function() {
          bFullDescPopupShouldBeVisible_Popup = true;
        },
        function() {
          bFullDescPopupShouldBeVisible_Popup = false;
          setTimeout(function(){
            if (!bFullDescPopupShouldBeVisible_Popup && !bFullDescPopupShouldBeVisible_Link) {
              $(objEleDescCont).hide();
            }
          }, 1000);
        }
      );
    }
  }
  
  // ============================================================================================================
  // Re-Search (paging, refine-bys)
  // ============================================================================================================
  function hookReSearchEvents() {
    var arrDropDowns = [ "sortOrder", "pageNo", "theText", "category", "boardbasis", "MaxPrice", "SelLocation" ];
    for (var intIndex = 0; intIndex < arrDropDowns.length; intIndex++) { 
      $("div.SearchRefinements select[name=\"" + arrDropDowns[intIndex] + "\"]").change(function() {
        reSearchFromEle(this.name, this.value);
        return false;
      });
    }
    $("div.SearchRefinements input.LocationCheckbox").change(function(){
      reSearchFromEle(this.name, (this.checked ? this.value : ""));
      return false;
    });
    $("div.SearchRefinements input.btnFindInSearch").click(function(){
      var $FindInSearchIpt = $("div.SearchRefinements input[name=\"FindInSearch\"]");
      if ($FindInSearchIpt.length == 0) {
        alert("Unable to find textbox element");
        return false;
      }
      var strName = $("div.SearchRefinements input[name=\"FindInSearch\"]").val();
      reSearchFromEle("FindInSearch", $.trim(strName));
      return false;
    });
    
    $("div.HotelSearchResults div.Paging li a").click(function() {
      // 2010-08-26 DWR: Ensure SuperHotelId filter is disabled (Search Proxy change means that the "All
      // Hotels" list is generated BEFORE the specific-SuperHotelId-filter - where specified - is applied
      // which means that the hotel dropdown remains populated after a single hotel is selected, but it
      // also means that the paging links will be generated - if required - and if we don't remove the
      // SuperHotelId filter then trying to go to page 2, 3, etc.. will fail as there will technically
      // only be one result (and so an error message will be displayed)
      reSearch({ "pageNo": $.trim(this.innerHTML), "theText": "" });
      return false;
    });

    function reSearchFromEle(strName, strValue) {
      // If not explicitly selecting page, jump to 1st page (if we're on page 4 and then apply a filter
      // which only results in 2 pages then we'll get an "No Results Found" message if we try to display
      // page 4 data). Also undo "theText" value (which refers to a specified Super Hotel Id) if applying
      // any other filter, it probably doesn't make sense to keep it on.
      var objUpdateValues = {};
      objUpdateValues[strName] = strValue;
      if (strName !== "pageNo") {
        objUpdateValues.pageNo = 1;
      }
      if (strName !== "theText") {
        objUpdateValues.theText = "";
      }
      reSearch(objUpdateValues);
    }
  }

  function reSearch(objUpdateValues, strMessage) {
    if (!reSubmitForm("ReSearch", objUpdateValues, true, strMessage)) {
      alert("Error: Unable to find ReSearch form");
      return;
    }
  }

  // ============================================================================================================
  // Re-Search with one less child, if possible - if no results were returned, this method will be called: if
  // there were children in the search we'll try re-searching but replacing the eldest child with an adult
  // ============================================================================================================
  function reSearchWithLessChildren() {
    if (objSearchCriteria.ChildrenPerRoom <= 0) {
      return false;
    }
    
    // Sort array with youngest first, then trim the last one out
    var arrChildAges = objSearchCriteria.ChildAges;
    arrChildAges.sort(function(x, y){
    	var sngX = parseFloat(x);
    	var sngY = parseFloat(y);
    	if (isNaN(sngX) && isNaN(sngY)) {
    		return 0;
    	}
    	else if (isNaN(sngY)) {
    		return -1;
    	}
    	else if (isNaN(sngX)) {
    		return 1;
    	}
    	return (sngX < sngY ? -1 : (sngY < sngX ? 1 : 0));
    });
    arrChildAges.splice(arrChildAges.length - 1, 1);
    
    var objUpdateValues = {
      AdultsPerRoom: objSearchCriteria.AdultsPerRoom + 1,
      ChildrenPerRoom: objSearchCriteria.ChildrenPerRoom - 1
    };
    for (var intIndex = 0; intIndex < 4; intIndex++) {
      objUpdateValues["roomType1Age" + (intIndex + 1)] = "";
    }
    for (var intIndex = 0; intIndex < arrChildAges.length; intIndex++) {
      objUpdateValues["roomType1Age" + (intIndex + 1)] = arrChildAges[intIndex];
    }
    
    $(document).ready(function(){
      var strMessage = 
        "There were no results for " + 
        (objSearchCriteria.AdultsPerRoom) + " adult" + (objSearchCriteria.AdultsPerRoom > 1 ? "s" : "") + " " +
        "and " +
        (objSearchCriteria.ChildrenPerRoom) + " child" + (objSearchCriteria.ChildrenPerRoom > 1 ? "ren" : "") + ", " +
        "looking for rooms for " +
        (objSearchCriteria.AdultsPerRoom + 1) + " adult" + ((objSearchCriteria.AdultsPerRoom + 1) > 1 ? "s" : "") + " " +
        "and " +
        (objSearchCriteria.ChildrenPerRoom - 1) + " child" + ((objSearchCriteria.ChildrenPerRoom - 1) > 1 ? "ren" : "") + ".. ";
      reSearch(objUpdateValues, strMessage);
    });
    return true;
  }  

  // ============================================================================================================
  // Re-Search with no children (replacing them all with adults)
  // ============================================================================================================
  function reSearchWithZeroChildren() {
    var objUpdateValues = {
      AdultsPerRoom: objSearchCriteria.AdultsPerRoom + objSearchCriteria.ChildrenPerRoom,
      ChildrenPerRoom: 0,
      ChildAges: null
    };
    reSearch(objUpdateValues, null);
  }
  
  // ============================================================================================================
  // Load Day Rate Price Breakdown and display in popup
  // ============================================================================================================
  var intPriceBreakDownRequestCount = 0;
  function getPriceBreakdown(objRoomDetails, objEleSrc) {
    var $row = $(objEleSrc).closest("tr");
    if ($row.length === 0) {
      alert("Unable to identify Room row");
      return;
    }
    
    var $cont = $row.closest("div.ResultEntry");
    if ($cont.length === 0) {
      alert("Unable to identify Hotel container");
      return;
    }
    
    // Remove any other popups (whether already displaying data or displaying loading symbol) - this way
    // we don't have to worry about only showing one popup or only updating the most recent one when
    // data is returned from the ajax call (since each ajax called is tied to a popup and older
    // popups are removed)
    var $masterCont = $cont.closest("div.HotelSearchResultsDetails");
    $masterCont.find(".PriceBreakdownPopup").remove();
    
    var objElePopup = document.createElement("div");
    objElePopup.className = "PriceBreakdownPopup";
    objElePopup.style.position = "absolute";
    objElePopup.style.left = $row.offset().left + "px";
    objElePopup.style.top = ($row.offset().top + $row.height()) + "px";
    objElePopup.style.width = $row.width() + "px";
    objElePopup.style.height = $row.height() + "px";
    objElePopup.innerHTML = "<div class=\"PriceBreakdownPopupContent LoadingPriceBreakdown\"><img alt=\"Please Wait\" src=\"images/ajax_busy2.gif\" /></div>";
    $cont[0].appendChild(objElePopup);
    $(objElePopup).click(function() {
      $(this).remove();
    });
    
    var strChildAges = (objSearchCriteria.ChildAges == null ? "" : objSearchCriteria.ChildAges.join(","));
    $.get(
      "searchHotel/ajax/getPriceBreakdownAjax.asp",
      {
        AvailResultId: objRoomDetails.AvailabilityResultId,
        HotelId: objRoomDetails.HotelId,
        SupplierCode: objRoomDetails.SupplierCode,
        SvcCode: objRoomDetails.SvcCode,
        StartDate: formatDate(objSearchCriteria.StartDate),
        Nights: objSearchCriteria.NumNights,
        Units: objSearchCriteria.UnitCount,
        AdultsPerRoom: objSearchCriteria.AdultsPerRoom,
        ChildrenPerRoom: objSearchCriteria.ChildrenPerRoom,
        ChildAges: strChildAges,
        AvailPriceGBP: objRoomDetails.PriceGBP
      },
      function(data, status) {
        if (status !== "success") {
          showPriceBreakdown(objElePopup, "Unable to retrieve Price Breakdown data");
          return;
        }
        var objData;
        try {
          eval("objData = " + data);
        }
        catch(e) {
          showPriceBreakdown(objElePopup, "Unable to retrieve Price Breakdown data (received invalid data)");
          return;
        }
        showPriceBreakdown(objElePopup, objData.ErrorInfo, (objData.CurrencySymbol == null ? objData.CurrencyCode : objData.CurrencySymbol), objData.DayRateData);
      }
    );

    function showPriceBreakdown(objElePopup, strErrorInfo, strCurrency, arrDayRates) {
      var strContent;
      if (strErrorInfo !== null) {
        strContent = "Error: " + strErrorInfo;
      }
      else if (arrDayRates === null) {
        strContent = "Error: No rates data returned";
      }
      else {
        var dtCurrent = objSearchCriteria.StartDate;
        var arrWeeks = [];
        var intWeekIndex = 0;
        for (var intIndex = 0; intIndex < arrDayRates.length; intIndex++) {
          var intDay = dtCurrent.getDay();
          if (intDay == 0) {
            // Push Sunday (returns zero from getDay) to the end
            intDay = 7;
          }
          // Reset intDay to be zero-based
          intDay--;
          if ((intDay == 0) && (arrWeeks.length > 0)) {
            intWeekIndex++;
          }
          if (typeof(arrWeeks[intWeekIndex]) === "undefined") {
            arrWeeks[intWeekIndex] = [];
          }
          arrWeeks[intWeekIndex][intDay] = arrDayRates[intIndex];
          dtCurrent.setDate(dtCurrent.getDate() + 1);
        }
        var arrContent = [];
        arrContent.push("<table><tr><th>Mon</th><th>Tue</th><th>Wed</th><th>Thu</th><th>Fri</th><th>Sat</th><th>Sun</th></tr>");
        for (var intWeekIndex = 0; intWeekIndex < arrWeeks.length; intWeekIndex++) {
          arrContent.push("<tr>");
          for (var intIndex = 0; intIndex < 7; intIndex++) {
            var strRateValue = arrWeeks[intWeekIndex][intIndex];
            arrContent.push("<td>");
            if (typeof(strRateValue) === "undefined") {
              arrContent.push(" ");
            }
            else {
              arrContent.push(strCurrency);
              arrContent.push(formatPrice(strRateValue));
            }
            arrContent.push("</td>");
          }
          arrContent.push("</tr>");
        }
        arrContent.push("</table>");
        strContent = arrContent.join("");
      }
      
      // Note: Can actually click anywhere on container to close so this link doesn't need hooking into
      strContent += "<p class=\"Close\"><a href=\"#\" onclick=\"return false;\">Click to close</a></p>";

      // Note: It's possible that the popup we want to populate has been removed by jQuery above (if
      // another Price Breakdown link was clicked) but this doesn't seem to cause any problems here.
      // To be on the safe side, we'll wrap the innerHTML assignment in try..catch - if the element
      // has been removed then we don't lose anything by ignoring this error as we're no longer
      // interested in the element's contents!
      try {
        objElePopup.innerHTML = "<div class=\"PriceBreakdownPopupContent\">" + strContent + "</div>";
      }
      catch(e) { }
    }
  }
  
  
  // ============================================================================================================
  // Booking
  // ============================================================================================================
  function book(objData, objEleSrc) {
    // Determine whether this booking's Location (if present) isn't in the Compulsory Transfer list
    var bRequireTransfer = false;
    if (objData.LocationId !== null) {
      if (!arrCompulsoryTransferLocations) {
        alert("Unable to retrieve Compulsory Transfer Lookup data");
        return;
      }
      for (var intIndex = 0; intIndex < arrCompulsoryTransferLocations.length; intIndex++) {
        if (arrCompulsoryTransferLocations[intIndex] == objData.LocationId) {
          bRequireTransfer = true;
          break;
        }
      }
    }
    
    // Determine whether any Transfer options are visible for the current Romo
    // - Note: Although the "Book" button will appear in the Transfer content, the click event
    //   uses the Room "Book" button so the parent row will be the Room row
    var $parentRow = $(objEleSrc).closest("tr");
    var $transferRow = $parentRow.next("tr.AvailableTransfers");
    
    // If a Transfer if compulsory, ensure we have one selected!
    // Note: Also ensure Transfers are for THIS booking..
    if (bRequireTransfer) {
    
      // Ensure that the next row in the table is a Transfers row
      if ($transferRow.length === 0) {
        alert("Hotels in this resort can not be booked without Transfers");
        return;
      }
      
      // Ensure that selection(s) have been made
      if ($transferRow.find("input.TransferOption:checked").length === 0) {
        alert("Hotels in this resort can not be booked without Transfers");
        return;
      }
    }
    
    // Ensure that any Transfer selections for other Rooms are removed (otherwise they'll end up
    // getting booked with this Room when they shouldn't be!) - if the Transfers are for this
    // Room, don't hide them
    if ($transferRow.length === 0) {
      removeTransferRows();
    }
    
    // Note: The next page doesn't yet accept "SupplierCode" directly, it uses legacy code which pieces it
    // together from "SearchId" and "H2H" so we'll pass in both formats
    var objUpdateValues = {
      "HotelId": objData.HotelId,
      "RoomName": objData.RoomName,
      "RoomType": objData.RoomType,
      "MealPlan": objData.MealPlan,
      "AvailResultId": objData.AvailResultId,
      "SupplierCode": objData.SupplierCode,
      "SvcCode": objData.SvcCode,
      "TotalHotelRate": objData.PriceGBP
    };
    objUpdateValues.icStatus = (objData.OnRequest ? "0" : "1");
    
    var strSupplierCode = ("" + objData.SupplierCode);
    var arrSupplierCodeSegments = strSupplierCode.split("-");
    var strH2H = arrSupplierCodeSegments[arrSupplierCodeSegments.length - 1];
    var strSearchId = strSupplierCode.substring(0, strSupplierCode.length - (strH2H.length + 1));
    
    objUpdateValues.H2H = strH2H;
    objUpdateValues.SearchId = strSearchId;

    reSubmitForm("Book", objUpdateValues, false, null);    
  }
  
  // ============================================================================================================
  // Load Transfer data
  // - Note [2010-08-09 DWR]: Instead of storing all the costs data in hidden cells, it would've been better to
  //   store everything in javascript data somewhere. If maintenance of this approach gets too arduous it's
  //   probably worth moving to a solution of that form.
  // ============================================================================================================
  var objTransferDataCache = {};
  function hookTransferButtons() {
    $("div.HotelSearchResults div.HotelSearchResultsDetails table.Rooms td.Transfers a").click(function(){
      var locationId = getLocationIdForTransferButton(this);
      if (locationId === null) {
        alert("Unable to retrieve LocationId for this Hotel");
        return;
      }
      var objEleSrc = this;
      var cachedData = objTransferDataCache[locationId];
      if (typeof(cachedData) !== "undefined") {
        showTransfers(objEleSrc, null, cachedData);
        return;
      }
      showTransfersLoading(objEleSrc);
      $.get(
        "searchHotel/ajax/getTransfersAjax.asp",
        {
          LocationId: locationId,
          CityCode: objSearchCriteria.CityCode,
          DateFrom: formatDate(objSearchCriteria.StartDate),
          DateTo: formatDate(dateAddDays(objSearchCriteria.StartDate, objSearchCriteria.NumNights)),
          Adults: objSearchCriteria.AdultsPerRoom * objSearchCriteria.UnitCount,
          Children: objSearchCriteria.ChildrenPerRoom * objSearchCriteria.UnitCount,
          ChildAges: (objSearchCriteria.ChildAges == null ? "" : objSearchCriteria.ChildAges.join(","))
        },
        function(data, status) {
          if (status !== "success") {
            showTransfers(objEleSrc, "Unable to retrieve Transfer data", null);
            return;
          }
          var objData;
          try {
            eval("objData = " + data);
          }
          catch(e) {
            showTransfers(objEleSrc, "Unable to retrieve Transfer data (received invalid data)", null);
            return;
          }
          var objTransferData = {
            "DateRestrictedResults": objData.DateRestrictedResults,
            "Transfers": objData.Transfers
          };
          if (objData.ErrorInfo === null) {
            objTransferDataCache[locationId] = objTransferData;
          }
          showTransfers(objEleSrc, objData.ErrorInfo, objTransferData);
        }
      );
      return false;
    });
     
    function showTransfers(objEleSrc, strErrorInfo, objTransferData) {
      // The DateRestrictedResults flag means that the booking is not sufficiently far in advance and so
      // less results are being returned (if only the start date was too soon then we may have only
      // "FromAccom" results - if both dates were too soon then we'll have no results)
      var arrTransfers = objTransferData.Transfers;
      var bDateRestrictedResults = objTransferData.DateRestrictedResults;
    
      // Try to grab cost for hotel (required by getTransfersContent method)
      var $roomRow = $(objEleSrc).closest("tr");
      var $cellHotelTotal = $roomRow.find("td.CostRaw");
      if ($cellHotelTotal.length === 0) {
        alert("Unable to locate Hotel Booking details");
        return;
      }
      var sngHotelTotal = parseFloat($cellHotelTotal[0].innerHTML, 10);
      if (isNaN(sngHotelTotal)) {
        alert("Unable to locate Hotel Booking Cost details");
        return;
      }
    
      // Display Transfer content for current Room
      var objEleTransferContent = getTransferRowContentContainer(objEleSrc);
      var strContent;
      if (strErrorInfo !== null) {
        strContent = "<p class=\"Error\">" + strErrorInfo + "</p>";
      }
      else {
        strContent = getTransfersContent(arrTransfers, sngHotelTotal);
        if (bDateRestrictedResults) {
          strContent = "<p class=\"DateRestrictedResults\">Transfer must be booked at least three days in advance, this may have reduced the number of available options.</p>" + strContent;
        }
      }
      objEleTransferContent.innerHTML = strContent;
      
      // Hide Room row "Book" button since we now have a Room-with-Transfer(s) Book button
      if ((arrTransfers !== null) && (arrTransfers.length > 0)) {
        // 2010-08-24 DWR: Only do this if some Transfers were actually available! Otherwise there
        // WON'T be a new Room-with-Transfer(s) Book button
        $roomRow.find("td.Book a").hide();
      }
      
      // Hook up events for the new Transfer content
      setTimeout(function(){
        
        // Click Transfer "Book" button calls Room "Book" button event
        $(objEleTransferContent).find("a.BookWithTransfers").click(function(){
          var $bookTransfer = $(this);
          var $bookHotel = $bookTransfer.closest("table").closest("tr").prev("tr").find("td.Book a");
          if ($bookHotel.length === 0) {
            alert("Unable to locate Booking details");
          }
          else {
            $bookHotel.click();
          }
          return false;
        });
        
        // Selected/unselecting Transfers should update the Hotel-with-Transfer(s) total
        $(objEleTransferContent).find("input.TransferOption").change(function(){
        
          if (this.checked) {
            var strName = ("" + this.name);
            if ((strName.substr(0, 19) === "AdditionalCheckbox-") && (strName.indexOf("**") !== -1)) {
              strName = strName.split("**")[0];
              var arrSegments = strName.split("-");
              var strTransferId = arrSegments[arrSegments.length - 1];
              for (var intIndex = 0; intIndex < arrTransfers.length; intIndex++) {
                var objTransfer = arrTransfers[intIndex];
                if ((objTransfer.Id === strTransferId) && (objTransfer.Warning !== null)) {
                  setTimeout(
                    function() { alert(objTransfer.Warning); },
                    10
                  );
                }
              }
            }
          }
          
          
          // Try to get Hotel cost
          var $cellHotelTotal = $(this).closest("table").closest("tr").prev("tr").find("td.CostRaw");
          if ($cellHotelTotal.length === 0) {
            alert("Unable to locate Hotel Booking details");
            return;
          }
          var sngTotal = parseFloat($cellHotelTotal[0].innerHTML, 10);
          if (isNaN(sngTotal)) {
            alert("Unable to locate Hotel Booking Cost details");
            return;
          }
          
          // Look for selected Transfers
          $(this).closest("table").find("tr").each(function(){
            if ($(this).find("input.TransferOption:checked").length > 0) {
              var $cellCostRaw = $(this).find("td.CostRaw");
              if ($cellCostRaw.length === 0) {
                alert("Unable to locate Transfer Booking details");
                return;
              }
              var sngTotalTransfer = parseFloat($cellCostRaw[0].innerHTML, 10);
              if (isNaN(sngTotalTransfer)) {
                alert("Unable to locate Transfer Booking Cost details");
                return;
              }
              sngTotal += sngTotalTransfer;
            }
          });
          
          // Render total
          var $cellGrandTotal = $(this).closest("table").find("tr.TotalRow td.Total");
          if ($cellGrandTotal.length === 0) {
            alert("Unable to locate Grand Total element");
          }
          else {
            $cellGrandTotal[0].innerHTML = objSearchCriteria.SiteCurrencySymbol + formatPrice(sngTotal);
          }
          
        });
        
      }, 10);
    }
    
    function getTransfersContent(arrTransfers, sngHotelTotal) {
      // The data regarding the selected Transfers is communicated through some hidden inputs that are matched
      // up to the Names of checkboxes of selected Transfers. This could probably be tidied up but we're
      // matching the legacy behaviour at this point.
      if ((arrTransfers === null) || (arrTransfers.length == 0)) {
        return "<p class=\"NoTransfers\">No Transfers Available</p>";
      }
      arrOut = [];
      arrOut.push("<table>");
      for (var intIndex = 0; intIndex < arrTransfers.length; intIndex++) {
        var objTransfer = arrTransfers[intIndex];
        arrOut.push("<tr>");
        arrOut.push("<td class=\"Mode\">");
        arrOut.push(objTransfer.Mode);
        arrOut.push("</td>");
        arrOut.push("<td class=\"Description\">");
        arrOut.push(objTransfer.Description);
        arrOut.push("</td>");
        arrOut.push("<td class=\"Cost\">");
        var currencySymbol = $.trim("" + objTransfer.CurrencySymbol);
        if (currencySymbol === "") {
          currencySymbol = objTransfer.CurrencyCode
        }
        arrOut.push("<span class=\"Currency\">");
        arrOut.push(currencySymbol);
        arrOut.push("</span>");
        arrOut.push("<span class=\"Value\">");
        arrOut.push(formatPrice(objTransfer.BookingPrice));
        arrOut.push("</span>");
        arrOut.push("</td>");
        arrOut.push("<td class=\"Book\">");
        arrOut.push("<input type=\"checkbox\" class=\"TransferOption\" name=\"" + "AdditionalCheckbox-" + getTransferInputBaseName(objTransfer, intIndex) + "\" />");
        arrOut.push("</td>");
        arrOut.push("<td class=\"CostRaw\">");
        arrOut.push(objTransfer.BookingPrice);
        arrOut.push("</td>");
        arrOut.push("</tr>");
      }
      arrOut.push("<tr class=\"TotalRow\">");
      arrOut.push("<td colspan=\"2\">Total Price including Transfers (if selected)</td>");
      arrOut.push("<td class=\"Total\">");
      arrOut.push(objSearchCriteria.SiteCurrencySymbol);
      arrOut.push(formatPrice(sngHotelTotal));
      arrOut.push("</td>");
      arrOut.push("<td class=\"Book\"><a href=\"#\" class=\"BookWithTransfers\">Book</a></td>");
      arrOut.push("</tr>");
      arrOut.push("</table>");
      arrOut.push("<p class=\"FootNote\">All Transfers are priced per party.</p>");
      for (var intIndex = 0; intIndex < arrTransfers.length; intIndex++) {
        // Price should be listed here in GBP as the next page will apply any required currency conversion
        var objTransfer = arrTransfers[intIndex];
        arrOut.push(getTransferDataInput("IATACode", objTransfer, intIndex, objTransfer.AirportCode));
        arrOut.push(getTransferDataInput("AdditionalRate", objTransfer, intIndex, formatPrice(objTransfer.BookingPriceGBP)));
        arrOut.push(getTransferDataInput("AdditionalRateIdentifier", objTransfer, intIndex, getRateDetailString(objTransfer, intIndex)));
        arrOut.push(getTransferDataInput("AdditionalDescription", objTransfer, intIndex, objTransfer.Description));
      }
      return arrOut.join("");
    }
    
    function showTransfersLoading(objEleBtn) {
      var objEleTransferContent = getTransferRowContentContainer(objEleBtn);
      objEleTransferContent.innerHTML = "<div class=\"LoadingTransfers\"><img alt=\"Please Wait\" src=\"images/ajax_busy2.gif\" /></div>";
    }

    function getTransferRowContentContainer(objEleBtn) {
      // Remove any existing Rows
      removeTransferRows();
      
      // Look for related Room row
      var $rowRoom = $(objEleBtn).closest("tr");
      
      // Create new Transfee row after Room row
      var rowTransfer = document.createElement("tr");
      rowTransfer.className = "AvailableTransfers";
      
      // Create a new cell which spans the entire row and return reference
      $(rowTransfer).insertAfter($rowRoom);
      var cellTransfer = document.createElement("td");
      cellTransfer.colSpan = $rowRoom.children("td").length;
      rowTransfer.appendChild(cellTransfer);
      return cellTransfer;
    }
    
    function getLocationIdForTransferButton(objEleBtn) {
      var $row = $(objEleBtn).closest("tr");
      var arrClass = ("" + $row[0].className).split(" ");
      for (var intIndex = 0; intIndex < arrClass.length; intIndex++) {
        var strClass = arrClass[intIndex];
        if (strClass.substring(0, "Location".length) === "Location") {
          var strLocId = strClass.substring("Location".length, strClass.length);
          var intLocId = parseInt(strLocId, 10);
          if (!isNaN(intLocId)) {
            return intLocId;
          }
        }
      }
      return null;
    }
    
    function getTransferDataInput(strInputName, objTransfer, intTransferIndex, strValue) {
      return "<input " + 
        "type=\"hidden\" " + 
        "name=\"" + strInputName + "-" + getTransferInputBaseName(objTransfer, intTransferIndex) + "\" " + 
        "value=\"" + strValue + "\" " + 
        "/>";
    }
    
    function getTransferInputBaseName(objTransfer, intTransferIndex) {
      return "0-" + objTransfer.Id + "**" + intTransferIndex;
    }
    
    function getRateDetailString(objTransfer, intTransferIndex) {
      var strChildAges;
      if (objSearchCriteria.ChildAges == null) {
        strChildAges = "";
      }
      else {
        strChildAges = objSearchCriteria.ChildAges.join(",");
      }
      return "" + 
        objTransfer.Id + "**" + 
        intTransferIndex + "**" +
        objTransfer.SvcCode + "**" + 
        objTransfer.SupplierCode + "**" + 
        objTransfer.ResultId + "**" + 
        strChildAges; 
    }
  }

  function removeTransferRows() {
    // Note: Ensure that the "Book" button is visible for all Room rooms - we may have hidden it for
    // a Room row with Transfer content..
    $("div.HotelSearchResults div.HotelSearchResultsDetails table.Rooms tr.AvailableTransfers").remove();
    $("div.HotelSearchResults div.HotelSearchResultsDetails table.Rooms tr td.Book a").show();
  }

  // ============================================================================================================
  // Shared
  // ============================================================================================================
  function getHotelIdFromTableClassName(strClassName) {
    if (strClassName) {
      var arrClass = ("" + strClassName).split(" ");
      for (var intIndex = 0; intIndex < arrClass.length; intIndex++) {
        if (arrClass[intIndex].substring(0, 5) == "Hotel") {
          return arrClass[intIndex].substring(5, arrClass[intIndex].length);
        }
      }
    }
    return null;
  }
  
  function reSubmitForm(strFormClass, objUpdateValues, bShowSearchAnimation, strMessage) {
    var $form = $("form." + strFormClass);
    if ($form.length === 0) {
      return false;
    }
    var form = $form[0];
    for (strName in objUpdateValues) {
      var $ipt = $(form).find("input." + strName);
      var ipt;
      if ($ipt.length > 0) {
        ipt = $ipt[0];
      }
      else {
        ipt = document.createElement("input");
        ipt.className = strName;
        ipt.name = strName;
        ipt.type = "hidden";
        form.appendChild(ipt);
      }
      ipt.value = objUpdateValues[strName];
    }

    if (strMessage) {
      $(".LoadingMessage").html(strMessage);
    }
    if (bShowSearchAnimation) {
      StartSearch();
      setTimeout(function() {
        form.submit();
      }, 10);
    }
    else {
      form.submit();
    }
    return true;
  }
  
  function dateAddDays(dtIn, intDays) {
    var dtNew = new Date();
    dtNew.setFullYear(dtIn.getFullYear());
    dtNew.setMonth(dtIn.getMonth());
    dtNew.setDate(dtIn.getDate() + intDays);
    return dtNew;
  }
  
  function formatDate(dtIn) {
    return dtIn.getFullYear() + "-" + (dtIn.getMonth() + 1) + "-" + dtIn.getDate();
  }

  function formatPrice(varIn) {
    if (varIn === null) {
      return null;
    }
    var sngIn = parseFloat(varIn, 10);
    if (isNaN(sngIn)) {
      throw { Message: "formatPrice: Invalid input - not null and not a number [" + varIn + "]" };
    }
    sngIn = Math.round(sngIn * 100) / 100;
    var strIn = "" + sngIn;
    if (strIn.indexOf(".") === -1) {
      strIn += ".";
    }
    while (strIn.indexOf(".") >= (strIn.length - 2)) {
      strIn += "0";
    }
    return strIn;
  }

  // ============================================================================================================
  // Initialise when Page Ready
  // ============================================================================================================
  $(document).ready(init);
  return {
    Book: book,
    ReSearchWithLessChildren: reSearchWithLessChildren,
    ReSearchWithZeroChildren: reSearchWithZeroChildren,
    GetPriceBreakdown: getPriceBreakdown
  }

})();

