//  vacation rentals namespace
ta.vr = {};

/* The number of attempts to retrieve the availability */
if (typeof availabilityAttemptCount == 'undefined') var availabilityAttemptCount = 0;

/*
 * When the #AVAILABILITY_CALENDAR div is loaded create a calendar
 */
rules['#AVAILABILITY_CALENDAR'] = function(/* HTMLElement */ element,
                                           /* Event */ event)
{
  //  if the availability data is available
  if (!disableCalendar)
  {
    //  if the data has been returned from the async HAC request
    if (notAvailableDateRanges)
    {
        element.empty();
        ta.vr.createAvailabilityCalendar(element);
    }
    //  else we need for the data to be returned from the async HAC request
    else
    {
        //  display a progress spinner if not already doing so
        if ($('availProg') == null)
        {
            var imgCntr = new Element('div', {
                'id':'availProg', 
                'class':'progresstab',
                'styles':{
                    'height':'108px',
                    'padding-top':'67px',
                    'padding-left':'353px'
                }
            });
            imgCntr.injectInside(element);
        }
        
        //  get the tab content element
        var tabContentElement = element.getParent('.tabContent');
            
        //  get the availability tab anchor
        var anchor = tabContentElement.getParent('.tabContainer').getElement('.current a');
        
        //  if there is no anchor then the availability tab was initially selected on page load
        //  and so we can re-use the current window location
        var href = (anchor != null ?
                    anchor.href :
                    window.location.href);
            
        //  add an 'xhr' request parameter to indicate what this is for
        var myHref = href + (href.indexOf('?') == -1 ? '?' : '&') + 'xhr=true&poll=true';
    
        //  poll until we get the data back
        (function(){ta.vr.pollAvailability(element, tabContentElement, myHref);}).delay(1000);
    }
  }
  else
  {
      //  create a dummy calendar
      element.empty();
      createDummyCalendar(element);
  }
};

/**
 * Poll availability data
 * @param {HTMLElement} availabilityCalendarElement the element to create the calendar from
 * @param {HTMLElement} tabContentElement the tab content element
 * @param {String} href the HREF to poll
 */
ta.vr.pollAvailability = function(/* HTMLElement */ availabilityCalendarElement,
                                  /* HTMLElement */ tabContentElement,
                                  /* String */ href)
{
    if (availabilityAttemptCount < 2)
    {
        // Make AJAX call to get content.
        new Ajax(href,
        {
            onComplete: function(txt, xml)
            {
                //  if the data is available
                if (txt.match(/new Date/))
                {
                    tabContentElement.empty();
                    tabContentElement.innerHTML = txt;
                    
                    // apply any defined behavior
                    window.behavior.apply(tabContentElement);
                    
                    //  notify anyone listening we are done
                    tabContentElement.fireEvent('onContentReplaced', [null, tabContentElement]);
                }
                //  else try again in a second
                else (function(){ta.vr.pollAvailability(availabilityCalendarElement, tabContentElement, href);}).delay(1000);
            },
            evalScripts:true
        }).request();            
            
        //  increment the attempt count
        availabilityAttemptCount++;
    }
    else
    {
      $('vrCalDiv').addClass('calDisabled');

      //  create a dummy calendar
      availabilityCalendarElement.empty();
      createDummyCalendar(availabilityCalendarElement);
    }
};

/**
 * Create an availability calendar for the next year marking availability
 * with 'notAvailableDateRanges'.
 * @param {HTMLElement} element the element to create the calendar with
 * @param {Date} the currently selected date
 */
ta.vr.createAvailabilityCalendar = function(/* HTMLElement */ element,
                                            /* Date */ selectedDate)
{
  //  only show 12 months
  var end = new Date();
  end.setMonth(end.getMonth()+12, 1);
  end.setDate(ta.util.date.DAYS_IN_MONTH[end.getMonth()]);

  //  grab and store the currency for formatting the calendar
  var currency = $("l1currency");
  if (currency)
  {
      ta.store('vr.currency', currencySymbol[currencyCodes[parseInt(crn.getValue())]]);
  }
  
  //  determine which formatter to use
  var theFormatter = $('vrCalPeriodWeekly').checked ?
                     ta.vr.weeklyCalendarFormatter :
                     ta.vr.dailyCalendarFormatter;
  
  //  create a new calendar
  var calendar = new ta.widgets.Calendar({
    firstDate:new Date(),
    lastDate:end,
    invalidDates:notAvailableDateRanges,
    useLinks:false,
    dayNames: true,
    offsetOther: 20,
    formatter:theFormatter,
    selectedDate:selectedDate,
    prevAction: 'ta.vr.calendarPrevious',
    nextAction: 'ta.vr.calendarNext'
  });
  element.adopt(calendar.container);
  element.calendar = calendar;
  calendar.update();
  calendar.reposition();
  
  //  update the legend
  var seasonalRates = ta.retrieve('vr.seasonalRates');
  if (seasonalRates)
  {
      var showLegend = $('vrCalPeriodDaily').checked ? seasonalRates.showDailyRates : seasonalRates.showWeeklyRates;
      $$('#vrCalLegend .toggle').each(function(legendItem)
      {
          legendItem.style.display = showLegend ? '' : 'none';
      });
  }
};

ta.vr.calendarPrevious = function(event, elmt){
  $(elmt).getParent('.calendar').getParent().calendar.prev();
};

ta.vr.calendarNext = function(event, elmt){
  $(elmt).getParent('.calendar').getParent().calendar.next();
};

/**
 * Formatter for dummy calendar cells
 * @param {Date} date the current date to format
 * @param {Boolean} if the current date is in the calendar's range
 * @param {Boolean} valid if the current day is valid (i.e. has availability)
 * @return a cell object for the calendar to render
 * @see Calendar#createMonth
 */
ta.vr.dummyCalendarFormatter = function(date, inRange, valid)
{
    return {cname:"disabled" , contents:"<span>"+date.getDate()+"</span><b>&nbsp;</b>"};
};

/**
 * Formatter for calendar cells when set to 'daily' rates
 * @param {Date} date the current date to format
 * @param {Boolean} if the current date is in the calendar's range
 * @param {Boolean} valid if the current day is valid (i.e. has availability)
 * @return a cell object for the calendar to render
 * @see Calendar#createMonth
 */
ta.vr.dailyCalendarFormatter = function(date, inRange, valid)
{
    var cell = {cname:"" , contents:"<span>"+date.getDate()+"</span>"};
    
    if (!inRange)
    {
        cell.cname='disabled';
        cell.contents += '<b>&nbsp;</b>';
    }
    else
    {
        var seasonalRates = ta.retrieve('vr.seasonalRates');
        if (seasonalRates)
        {
            var data = ta.vr.getSeasonalDailyRate(date);
            var formattedRate = data && data['dailyRate'] ? ta.vr.formatCalCurrency(data['dailyRate']) : null;
            cell.contents += '<b>' + (valid && formattedRate && seasonalRates.showDailyRates ? formattedRate : '&nbsp;') + '</b>';
        }
        else
        {
            cell.contents += '<b>&nbsp;</b>';
        }
        cell.cname = valid ? (data && data['dailyWeight'] ? data['dailyWeight'] : '') : 'unavail';
    }
    
    return cell;
};

/**
 * Formatter for calendar cells when set to 'daily' rates
 * @param {Date} date the current date to format
 * @param {Boolean} if the current date is in the calendar's range
 * @param {Boolean} valid if the current day is valid (i.e. has availability)
 * @param {Integer} currentDay the current day of the week (0-6)
 * @param {Object} calendarOptions the options used to create the calendar
 * @return a cell object for the calendar to render
 * @see Calendar#createMonth
 */
ta.vr.weeklyCalendarFormatter = function(date, inRange, valid, currentDay, calendarOptions)
{
    var cell = {cname:"" , contents:"<span>"+date.getDate()+"</span>"};
    
    if (!inRange)
    {
        cell.cname='disabled';
        cell.contents += '<b>&nbsp;</b>';
    }
    else
    { 
        var seasonalRates = ta.retrieve('vr.seasonalRates'); // may be null
        var showWeeklyRates = seasonalRates && seasonalRates.showWeeklyRates;
        var isFirstDayOfWeek = currentDay == 0;
        var isFirstDayOfMonth = date.getDate() == 1;
        var daysLeftInWeek = 7 - currentDay;
        var daysLeftInMonth = ta.util.date.DAYS_IN_MONTH[date.getMonth()] - date.getDate() + 1;
        var renderObj = {cssClass:''};
        var currentSeries = ta.vr.getCurrentWeeklySeries(date, calendarOptions);
        var isFirstDayInSeries = currentSeries && date.getTime() == currentSeries.startDate.getTime();
        var isLastDayInSeries = currentSeries && date.getTime() == currentSeries.endDate.getTime();
        
        //  if this is the first day of the week, first day of the month, or the first day in the series
        if (currentSeries && (isFirstDayOfWeek || isFirstDayOfMonth || isFirstDayInSeries))
        {
            //  calculate the days left in the series (including the current 'date')
            var daysLeftInSeries = ta.util.date.getDaysInRange(date, currentSeries.endDate);
            
            renderObj['periodValid'] = currentSeries.isValid;
            renderObj['daysWide'] = daysLeftInSeries < daysLeftInWeek ? daysLeftInSeries : daysLeftInWeek;
            renderObj['daysWide'] = renderObj['daysWide'] < daysLeftInMonth ? renderObj['daysWide'] : daysLeftInMonth;
            renderObj['isStartOfSeries'] = isFirstDayInSeries;
            renderObj['isEndOfSeries'] = renderObj['daysWide'] == daysLeftInSeries;
            renderObj['contents'] = currentSeries.weeklyRate;
            renderObj['weight'] = currentSeries.weeklyWeight;
            
            if (renderObj['contents'] && currentSeries.cellsNeeded > renderObj['daysWide'])
            {
                renderObj['contents'] = '&nbsp;';
            }
        }
        
        //  if we are starting a series on the first day of the week or month we need to wrap up the 
        //  previous series bar
        if (renderObj['isStartOfSeries'] && (currentDay == 0 || date.getDate() == 1))
        {
            renderObj['needWrapup'] = true;
        }

        //  if we have something to render
        if (renderObj['contents'] || renderObj['needWrapup'])
        {
            var buffer = [];
            buffer.push('<div class="bar">');
            
            if (renderObj['contents'])
            {
                //  if the bar ends on the last day of the week or month then don't close the bar since
                //  we will do that the morning of the first day of next week
                if ((currentDay + renderObj['daysWide'] == 7) || (date.getDate() + renderObj['daysWide'] - 1 == ta.util.date.DAYS_IN_MONTH[date.getMonth()]))
                {
                    renderObj['isEndOfSeries'] = false;
                }
                
                //  calculate the percentage width and margin
                renderObj['percentWide'] = (renderObj['daysWide'] * 100);
                renderObj['marginLeftPercent'] = 0;
                if (renderObj['isStartOfSeries'])
                {
                     renderObj['percentWide'] -= 60;
                     renderObj['marginLeftPercent'] = 50;
                }
                if (renderObj['isEndOfSeries'] && (currentDay + renderObj['daysWide'] != 7))
                {
                    renderObj['percentWide'] += 40;
                }
                
                //  if the cell is not wide enough to accommodate the content
                if (renderObj['percentWide'] < 80)
                {
                    renderObj['contents'] = '&nbsp;';
                }
                
                //  determine the div padding necessary to account for the day cell's border minus the div's borders
                renderObj['paddingPx'] = (renderObj['daysWide'] - 2) / 2;
                if (renderObj['paddingPx'] < 0) renderObj['paddingPx'] = 0;
                
                //  determine the css class
                renderObj['cssClass'] = renderObj['weight'];
                if (renderObj['isStartOfSeries'])
                {
                    renderObj['cssClass'] += " start";
                }
                if (renderObj['isEndOfSeries'])
                {
                    renderObj['cssClass'] += " end";
                }
                
    
                buffer.push('<div style="width:');
                buffer.push(renderObj['percentWide']);
                buffer.push('%; padding:0 ');
                buffer.push(renderObj['paddingPx']);
                buffer.push('px; margin-left:');
                buffer.push(renderObj['marginLeftPercent']);
                buffer.push('%;" class="');
                buffer.push(renderObj['cssClass']);
                buffer.push('">');
                buffer.push(renderObj['contents']);
                buffer.push('</div>');
            }
            
            if (renderObj['needWrapup'])
            {
                var yesterday = new Date(date.getFullYear(), date.getMonth(), date.getDate()-1);
                var yesterdaySeries = ta.vr.getCurrentWeeklySeries(yesterday, calendarOptions);
                
                //  if yesterday's weekly rate == null (and not '&nbsp;') then we don't need to render the wrapup
                if (yesterdaySeries && yesterdaySeries.weeklyRate != null)
                {
                    buffer.push('<div style="width:30%" class="end ');
                    buffer.push(yesterdaySeries && yesterdaySeries.weeklyWeight ? yesterdaySeries.weeklyWeight : 'unavail');
                    buffer.push('">&nbsp;</div>');
                }
            }
            buffer.push('</div>');
            cell.contents += buffer.join('');
        }
        //  else leave blank
        else
        {
            cell.contents += '<b>&nbsp;</b>';
        }
    }
    
    return cell;
};

/**
 * Get a seasonal daily rate for a given date.  Handles the case where
 * there are multiple overlapping seasons defined where each overlapping
 * season defines a different rate periodicity (i.e. daily in one season record
 * and weekly in another).
 * @param {Date} date
 * @return the seasonal rate or null if none
 */
ta.vr.getSeasonalDailyRate = function(/* Date */ date)
{
    var data = ta.vr.getSeasonalRatesData(date, false);
    var length = data.length;
    for (var i = 0; i < length; i++)
    {
        if (data[i]['dailyRate'])
        {
            return data[i];
        }
    }
    return null;
};

/**
 * Get data for a given date and period
 * @param {Date | [Date, Date]} the date or date range to get seasonal data for
 * @param {Boolean} firstOnly if we should stop at and return the first season's data
 * @return an array of season data or a single season if firstOnly=true
 */
ta.vr.getSeasonalRatesData = function(/* Date | [Date, Date] */ dates,
                                      /* Boolean */ firstOnly)
{
    var seasonalRates = ta.retrieve('vr.seasonalRates'); // may be null
    if (seasonalRates == null)
    {
        return firstOnly ? null : [];
    }
    var data = seasonalRates.data;
    var seasons = [];
    var currentDates = $type(dates) == 'array' ? dates : [dates, dates];
    var length = data.length;
    for (var i = 0; i < length; i++)
    {
        var seasonDates = [data[i].startDate, data[i].endDate];
        if (ta.util.date.rangesIntersect(currentDates, seasonDates))
        {
            if (firstOnly)
            {
                return data[i];
            }
            else
            {
                seasons.push(data[i]);
            }
        }
    }
    return seasons;
};

/**
 * Get information on the current valid series to which the date belongs or null if it
 * does not belong to a valid series
 * @param {Date} date the current date
 * @param {Object} calendarOptions the calendar options
 * @param get the current series data
 */
ta.vr.getCurrentWeeklySeries = function(/* Date */ date,
                                        /* Object */ calendarOptions)
{
    var invalidDates = calendarOptions.invalidDates;
    var weeklySeries = ta.retrieve('vr.cal.weeklySeries');
    if (!weeklySeries)
    {
        weeklySeries = ta.vr._computeAllSeries(invalidDates, calendarOptions);
        ta.store('vr.cal.weeklySeries', weeklySeries);
    }
    if (!weeklySeries) return null;
    
    for (var i = 0; i < weeklySeries.length; i++)
    {
        var s = weeklySeries[i];
        if ((s.startDate.getTime() <= date.getTime()) &&
            (s.endDate.getTime() >= date.getTime()))
        {
            return s;
        }
    }
    //console.warn("No series found: " + date.getFullYear() + '-' + date.getMonth() + '-' + date.getDate())
    return null;
};

/**
 * Compute all series for the calendar
 * @param {Array} invalidDates a set of dates and date ranges that are invalid (i.e. not available)
 * @param {Object} calendarOptions the calendar options
 */
ta.vr._computeAllSeries = function(/* Object */ invalidDates,
                                   /* Object */ calendarOptions)
{
    var showWeeklyRates = ta.retrieve('vr.seasonalRates').showWeeklyRates;
    var currentDate = new Date(calendarOptions.firstDate.getTime());
    var lastDateTime = calendarOptions.lastDate.getTime();
    var allSeries = [];
    var currentSeries = {startDate:new Date(currentDate.getTime()), endDate:null, daysLong:0, isValid:true, weeklyRate:null, weeklyWeight:null, cellsNeeded:0, hasTurnover:false};
    var validCount = 0;
    var lookingFor = true;
    while (currentDate.getTime() <= lastDateTime)
    {
        var currentIsValid = !ta.util.date.inDateSet(currentDate, invalidDates);
        var dayOfWeek = (currentDate.getDay() - jsGlobalDayOffset + 7) % 7;
        var turnover = ta.vr.getTurnover(currentDate);
        var isTurnover = (turnover != null) && (turnover == dayOfWeek);
        var lastDateOfCalendar = currentDate.getTime() == lastDateTime;

        //  if we have a turnover then we process turnover days and the last day of the calendar.  If there is no turnover
        //  then we process whenever we change to/from valid ranges for and the last day of the calendar.
        if (((turnover != null) && (isTurnover || lastDateOfCalendar)) ||
            ((turnover == null) && (currentIsValid != lookingFor || lastDateOfCalendar)))
        {
            //  if we just finished a series of valid dates
            if (lookingFor)
            {
                //  we need at least 7 valid days to make the series valid
                if (validCount < 7)
                {
                    //  if this is the turnover and we have anything less than a whole week, do not show
                    if (isTurnover)
                    {
                        var currentSeriesTurnover = ta.vr.getTurnover(currentSeries.startDate);
                        currentSeries.endDate = new Date(currentDate.getFullYear(), currentDate.getMonth(), currentDate.getDate()-1);
                        currentSeries.daysLong = ta.util.date.getDaysInRange(currentSeries.startDate, currentSeries.endDate);
                        currentSeries.hasTurnover = (currentSeriesTurnover != null);
                        currentSeries.weeklyRate = '&nbsp;';
                        currentSeries.weeklyWeight = 'unavail';
                        allSeries.push(currentSeries);

                        /* ==== DEBUG ==== */
                        /*console.log("---- INVALID 1 ----");
                        console.log("seriesStartDate = " + currentSeries.startDate.getFullYear() + '-' + currentSeries.startDate.getMonth() + '-' + currentSeries.startDate.getDate());
                        console.log("seriesEndDate = " + currentSeries.endDate.getFullYear() + '-' + currentSeries.endDate.getMonth() + '-' + currentSeries.endDate.getDate());
                        console.log("daysLong = " + ta.util.date.getDaysInRange(currentSeries.startDate, currentSeries.endDate));
                        console.log("validCount = " + validCount);
                        if (currentIsValid != lookingFor) console.log("~not what we are looking for~");
                        if (isTurnover) console.log("~is turnover~");
                        if (currentDate.getTime() == lastDateTime) console.log('~is last date~');
                        console.log("================================================");*/
                        /* ==== DEBUG ==== */
        
                        //  start a new series
                        currentSeries = {startDate:new Date(currentDate.getTime()), endDate:null, daysLong:0, isValid:currentIsValid, weeklyRate:null, weeklyWeight:null, cellsNeeded:0};
                        lookingFor = currentIsValid;
                        validCount = 0;
                    }
                    //  else if there is no turnover                    
                    else
                    {
                        //  keep going, but continue as if we were looking for invalid dates
                        lookingFor = false;
                    }
                }
                //  else we finished a series of valid dates
                else
                {
                    var currentSeriesTurnover = ta.vr.getTurnover(currentSeries.startDate);
                    currentSeries.isValid = true;
                    currentSeries.endDate = new Date(currentDate.getFullYear(), currentDate.getMonth(), currentDate.getDate()-1);
                    currentSeries.daysLong = ta.util.date.getDaysInRange(currentSeries.startDate, currentSeries.endDate);
                    currentSeries.hasTurnover = (currentSeriesTurnover != null);
                    currentSeries.weeklyRate = null;
                    currentSeries.weeklyWeight = 'peak';
                    
                    //  handle the case where we need to split it into seperate series, each with their own price
                    var seasonalData = ta.vr.getSeasonalRatesData([currentSeries.startDate, currentSeries.endDate], false);
                    if (seasonalData && seasonalData.length > 0)
                    {
                        var currentCount = 0;
                        var totalCnt = 0;
                        var seriesStartDate = null;
                        var seriesEndDate = null;
                        seasonalData.each(function(nextSeasonalData)
                        {
                            //  determine the current series start date
                            var sd = new Date(Math.max(nextSeasonalData.startDate.getTime(), currentSeries.startDate.getTime()));
                            if (!seriesStartDate) seriesStartDate = sd;
                            
                            //  determine the current series end date
                            var ed = new Date(Math.min(nextSeasonalData.endDate.getTime(), currentSeries.endDate.getTime()));
                            
                            //  count the number of days long and if we have enough series that collectivley have 7 day then
                            //  add them together as a complete series.  Not that if we have less than 7 days left after
                            //  the current series, we will need to include it in this one.
                            var daysLong = ta.util.date.getDaysInRange(sd, ed);
                            currentCount += daysLong;
                            totalCnt += daysLong;
                            if ((currentCount >= 7) && ((currentSeries.daysLong - totalCnt > 7) || (currentSeries.daysLong == totalCnt)))
                            {
                                seriesEndDate = ed;
                                var weightedRate = ta.vr._getWeeklySeriesWeightedRate(seriesStartDate, seriesEndDate, currentSeriesTurnover);
                                allSeries.push({
                                    startDate:seriesStartDate,
                                    endDate:seriesEndDate,
                                    daysLong:currentCount,
                                    isValid:true,
                                    weeklyRate:weightedRate.rate,
                                    weeklyWeight:weightedRate.weight,
                                    cellsNeeded:weightedRate.cellsNeeded,
                                    hasTurnover:(currentSeriesTurnover != null)
                                });
                                /* ==== DEBUG ==== */
                                /*console.log("---- VALID 1 ----");
                                console.log("seriesStartDate = " + seriesStartDate.getFullYear() + '-' + seriesStartDate.getMonth() + '-' + seriesStartDate.getDate());
                                console.log("seriesEndDate = " + seriesEndDate.getFullYear() + '-' + seriesEndDate.getMonth() + '-' + seriesEndDate.getDate());
                                console.log("daysLong = " + currentCount);
                                console.log("validCount = " + validCount);
                                console.log("weeklyRate = " + weightedRate.rate);
                                console.log("weeklyWeight = " + weightedRate.weight);
                                if (isTurnover) console.log("~is turnover~");
                                if (currentDate.getTime() == lastDateTime) console.log('~is last date~');
                                if (turnover != null && !isTurnover) console.error('ERROR:  Ending series not on the turnover: ' + seriesEndDate.getFullYear() + '-' + seriesEndDate.getMonth() + '-' + seriesEndDate.getDate())
                                console.log("================================================");*/
                                /* ==== DEBUG ==== */
                                    
                                seriesStartDate = null;
                                seriesEndDate = null;
                                currentCount = 0;
                            }
                        });
                    }
                    else
                    {
                        //  else just add the whole series
                        allSeries.push(currentSeries);
                        
                        /* ==== DEBUG ==== */
                        /*console.log("---- VALID 2 ----");
                        currentSeries.endDate = new Date(currentDate.getFullYear(), currentDate.getMonth(), currentDate.getDate()-1);
                        console.log("seriesStartDate = " + currentSeries.startDate.getFullYear() + '-' + currentSeries.startDate.getMonth() + '-' + currentSeries.startDate.getDate());
                        console.log("seriesEndDate = " + currentSeries.endDate.getFullYear() + '-' + currentSeries.endDate.getMonth() + '-' + currentSeries.endDate.getDate());
                        console.log("daysLong = " + ta.util.date.getDaysInRange(currentSeries.startDate, currentSeries.endDate));
                        console.log("validCount = " + validCount);
                        console.log("weeklyRate = " + currentSeries.weeklyRate);
                        console.log("weeklyWeight = " + currentSeries.weeklyWeight);
                        if (isTurnover) console.log("~is turnover~");
                        if (currentDate.getTime() == lastDateTime) console.log('~is last date~');
                        if (turnover != null && !isTurnover) console.error('ERROR:  Ending series not on the turnover: ' + seriesEndDate.getFullYear() + '-' + seriesEndDate.getMonth() + '-' + seriesEndDate.getDate())
                        console.log("================================================");*/
                        /* ==== DEBUG ==== */
                    }

                    //  start a new series
                    currentSeries = {startDate:new Date(currentDate.getTime()), endDate:null, daysLong:0, isValid:true, weeklyRate:null, weeklyWeight:null, cellsNeeded:0};
                    lookingFor = currentIsValid;
                    validCount = 0;
                }
            }
            //  else we finished a series of invalid dates
            else
            {
                var currentSeriesTurnover = ta.vr.getTurnover(currentSeries.startDate);
                currentSeries.isValid = false;
                currentSeries.endDate = new Date(currentDate.getFullYear(), currentDate.getMonth(), currentDate.getDate()-1);
                currentSeries.daysLong = ta.util.date.getDaysInRange(currentSeries.startDate, currentSeries.endDate);
                currentSeries.hasTurnover = (currentSeriesTurnover != null);
                currentSeries.weeklyRate = '&nbsp;';
                currentSeries.weeklyWeight = 'unavail';
                allSeries.push(currentSeries);

                /* ==== DEBUG ==== */
                /*console.log("---- INVALID 2 ----");
                console.log("seriesStartDate = " + currentSeries.startDate.getFullYear() + '-' + currentSeries.startDate.getMonth() + '-' + currentSeries.startDate.getDate());
                console.log("seriesEndDate = " + currentSeries.endDate.getFullYear() + '-' + currentSeries.endDate.getMonth() + '-' + currentSeries.endDate.getDate());
                console.log("daysLong = " + currentSeries.daysLong);
                console.log("validCount = " + validCount);
                if (currentIsValid != lookingFor) console.log("~not what we are looking for~");
                if (isTurnover) console.log("~is turnover~");
                if (currentDate.getTime() == lastDateTime) console.log('~is last date~');
                if (turnover != null && !isTurnover) console.error('ERROR:  Ending series not on the turnover: ' + currentSeries.endDate.getFullYear() + '-' + currentSeries.endDate.getMonth() + '-' + currentSeries.endDate.getDate());
                console.log("================================================");*/
                /* ==== DEBUG ==== */
                
                //  start a new series
                currentSeries = {startDate:new Date(currentDate.getTime()), endDate:null, daysLong:0, isValid:true, weeklyRate:null, weeklyWeight:null, cellsNeeded:0};
                lookingFor = currentIsValid;
                validCount = 0;
            }
        }
        
        if (turnover != null) lookingFor = lookingFor && currentIsValid;
        
        //  if valid, increment the count
        if (currentIsValid) validCount++;

        //  increment the date
        currentDate.setDate(currentDate.getDate()+1);
    };
    
    //  as a 2nd processing step (since the initial pass is already very complicated) we will join any adjascent
    //  ranges that have the same price or are both unavailable
    for (var i = 0; i < allSeries.length-1; i++)
    {
        var currentSeries = allSeries[i];
        var nextSeries = allSeries[i+1];
        
        //  don't merge any series with turnover dates
        if (currentSeries.hasTurnover || nextSeries.hasTurnover) continue;
        
        if ((!currentSeries.isValid && !nextSeries.isValid) ||
            (currentSeries.isValid == nextSeries.isValid && currentSeries.weeklyRate == nextSeries.weeklyRate))
        {
            currentSeries.endDate = nextSeries.endDate;
            currentSeries.daysLong = ta.util.date.getDaysInRange(currentSeries.startDate, currentSeries.endDate);
            allSeries.splice(i+1, 1);
            i--;
        }
    }
    
    return allSeries;
};

/**
 * Get the turnover for a given date
 * @param {Date} the date
 * @return {Integer} the turnover
 */
ta.vr.getTurnover = function(/* Date */ date)
{
    var currentSeasonalData = ta.vr.getSeasonalRatesData(date, true);
    if (currentSeasonalData && currentSeasonalData.turnover > 0)
    {
        var turnover = currentSeasonalData.turnover - 1 - jsGlobalDayOffset;
        if (turnover < 0) turnover += 7;
        return turnover;
    }
    else
    {
        return null;
    }
};

/**
 * Get a formatted weekly weighted rate for the series.  Add 'avg' to the return string if we averaged
 * values.
 * @param {Date} startDate the start date
 * @param {Date} endDate the end date
 * @param {Integer} turnover the turnover day or null for none
 * @return {String} a formatted weekly series weighted rate
 */
ta.vr._getWeeklySeriesWeightedRate = function(/* Date */ startDate,
                                              /* Date */ endDate,
                                              /* Integer */ turnover)
{
    var showWeeklyRates = ta.retrieve('vr.seasonalRates').showWeeklyRates;
    if (!showWeeklyRates)
    {
        //  don't render a bar
        return {'rate':null, 'weight':'peak', 'cellsNeeded':0};
    }
    
    var seasonalData = ta.vr.getSeasonalRatesData([startDate, endDate], false);
    if (seasonalData)
    {
        var weightedTotal = 0;
        var weightedCount = 0;
        seasonalData.each(function(nextSeasonalData)
        {
            if (!nextSeasonalData.weeklyRate) return;
            var seriesStartDate = new Date(Math.max(nextSeasonalData.startDate.getTime(), startDate.getTime()));
            var seriesEndDate = new Date(Math.min(nextSeasonalData.endDate.getTime(), endDate.getTime()));
            var duration = ta.util.date.getDaysInRange(seriesStartDate, seriesEndDate);
            weightedTotal += (nextSeasonalData.weeklyRate * duration);
            weightedCount += duration;
        });
        if (weightedCount > 0) weightedTotal /= weightedCount;
        //var isAveraged = seasonalData.length > 1 && seasonalData[0].weeklyRate != weightedTotal;
        var formattedRate = ta.vr.formatCalCurrency(weightedTotal);// + (isAveraged ? ' avg' : '');

        var seasonalRates = ta.retrieve('vr.seasonalRates') //  may be null
        var avgWeeklyRate = (seasonalRates ? seasonalRates.avgWeeklyRate : -1);
        var weeklyWeight = ta.vr.getWeeklyWeight(weightedTotal, avgWeeklyRate);
        
        //  if we have a rate
        if (avgWeeklyRate > 0 && weightedCount > 0)
        {
            if (turnover)
            {
                var rateMsg = ta.retrieve('vr.calendar_rate_starting_on_52'); // {0} starting on {1}*
                rateMsg = rateMsg.replace('{0}', formattedRate).replace('{1}', jsGlobalDaysAbbrev[turnover]);
                return {'rate':rateMsg, 'weight':weeklyWeight, 'cellsNeeded':3};
            }
            else
            {
                var rateMsg = ta.retrieve('vr.calendar_rate_52'); // {0}*
                rateMsg = rateMsg.replace('{0}', formattedRate);
                return {'rate':rateMsg, 'weight':weeklyWeight, 'cellsNeeded':2};
            }
        }
    }

    if (turnover)
    {
        var rateMsg = ta.retrieve('vr.calendar_week_starting_on_52'); // Week starting on {0}
        rateMsg = rateMsg.replace('{0}', jsGlobalDaysAbbrev[turnover]);
        return {'rate':rateMsg, 'weight':weeklyWeight, 'cellsNeeded':3};
    }
    else
    {
        //  don't render a bar
        return {'rate':null, 'weight':weeklyWeight, 'cellsNeeded':0};
    }
};

/**
 * Get the weekly weight
 * @param {Number} the weekly rate
 * @param {Number} the avg weekly rate
 * @return {String} the weekly weight
 */
ta.vr.getWeeklyWeight = function(/* Number */ weeklyRate,
                                 /* Number */ avgWeeklyRate)
{
    var weight = weeklyRate / avgWeeklyRate;
    return (weight > 1.15 ? 'peak' : (weight < 0.85 ? 'low' : 'avg'));
};

/**
 * Format currency for the calendar
 * @param {Number} amount the amount to format
 * @return {String} a formatted currency amount
 */
ta.vr.formatCalCurrency = function(/* Number */ amount)
{
    return ta.util.currency.formatCurrency(amount, ta.retrieve('vr.calCurrency'));    
};

/**
 * Update the availability calender to reflect newly selected options 
 */
ta.vr.updateAvailabilityCalendar = function()
{
    var element = $('AVAILABILITY_CALENDAR');
    //  grab the currently visible calendar date
    var currentDate = element.calendar ? element.calendar.currentMonth.date : null;
    
    //  only show the turnover disclaimer for the weekly view
    $('noTurnoverDisclaimer').style.display = $('vrCalPeriodWeekly').checked ? 'block' : 'none';
    
    //  clear and recreate the calendar
    element.empty();
    ta.vr.createAvailabilityCalendar(element, currentDate);
};

/**
 * Toggle from the calendar view to the detailed rates view
 */
ta.vr.viewDetailedRates = function()
{
    //  hide the calendar
    $('vrCalDiv').style.display = 'none';
    
    //  show/create the detailed rates
    $('vrDetailedRates').style.display = '';
};

/**
 * Toggle from the detailed rates view to the calendar view
 */
ta.vr.viewCalendar = function()
{
    //  hide the detailed rates
    $('vrDetailedRates').style.display = 'none';
    
    //  show the calendar
    $('vrCalDiv').style.display = '';
};

/*
 * Create a dummy calendar that is masked and has a message over it
 */
var createDummyCalendar = function(/* HTMLElement */ element,
                                   /* String */ message)
{
    //  create a dummy calendar with a mask over it and a message
    var end = new Date();
    end.setMonth(end.getMonth()+1, 1);
    end.setDate(ta.util.date.DAYS_IN_MONTH[end.getMonth()]);
    
    //  create a new calendar
    var calendar = new ta.widgets.Calendar({
      firstDate:new Date(),
      lastDate:end,
      invalidDates:notAvailableDateRanges,
      useLinks:false,
      dayNames: true,
      offsetOther: 20,
      formatter:ta.vr.dummyCalendarFormatter,
      prevAction: 'ta.vr.calendarPrevious',
      nextAction: 'ta.vr.calendarNext'
    });
    element.adopt(calendar.container);
    element.calendar = calendar;
    calendar.update();
    calendar.reposition();
    
    var vrCalMaskTarget = $('vrCalMaskTarget');
    var height = vrCalMaskTarget.getStyle('height');
    var overlay = new Element('div', {'id':'calendarOverlay', 'styles':{'height':height}});
    overlay.injectTop(vrCalMaskTarget);
    
    if (message)
    {
        var unavailable = new Element('div', {'class':'unavailable'});
        unavailable.innerHTML = message;
        element.appendChild(unavailable);
    }
}

/*
 * When the #VR_MAP div is loaded create a 'whats nearby' map
 */
rules['#VR_MAP'] = function(/* HTMLElement */ element,
                            /* Event */ event)
{
    //display loading div over map
    var loadDiv = new Element('div', {'class': 'loading'});
    new Element('img', {'src': 'http://cdn.tripadvisor.com/img2/generic/site/loop.gif'}).injectInside(loadDiv);
    loadDiv.injectInside($('VR_MAP').getElement('.whatsNearby .balance'));
    loadDiv.show();    
    //  load google maps script
    new Asset.javascript(mapsJs);
    var gLoadUrl = 'http://maps.google.com/maps?file=api&v=2.118&key=' + gKey + '&c&async=2&callback=initVacationRentalMap';
    new Asset.javascript(gLoadUrl);
    window.addListener('beforeunload', function(){
        window.addListener('unload', GUnload);
    });
};

/*
 * Callback for initializing the vacation rental map, called by rules['#VR_MAP']
 */
var initVacationRentalMap = function()
{
  var vrMap = $('VR_MAP');
  var mapDiv = $('vacationRentalMapDiv');
  
  //  attach event handlers to marker type togglers
  vrMap.getElements('.js_markerClass').each(function(elmt)
  {
    var input = elmt.getElement('input');
    var c = input.getProperty('class');
    elmt.addEvent('click', toggleMarkerType.bindAsEventListener(mapDiv, [input, c]));
    if (!input.checked) mapDiv.map.hideType(c);
  });

  mapDiv.errorDiv = vrMap.getElement('.js_error');
  mapDiv.recenter = vrMap.getElement('.js_mapHome');
  var ops = window[mapDiv.id];
  var servlet = window["pageServlet"];
  if (GBrowserIsCompatible() && ops) {
    var errorDiv = mapDiv.errorDiv;
  
    // Display the map, with some controls and set the initial location 
    mapDiv.map = new TAMap(mapDiv, {
      origLat: ops.lat,
      origLng: ops.lng,
      zoom: 14,
      hoverOffX: -122,
      homeOps: ops.geoMap ? {} : {customHover: {title: ops.title, url: getBaseMapsUrl("info", ops.locId, servlet)}},
      servlet: servlet,
      homeIcon: vrMap.hasClass('exactAddress')
    });
    mapDiv.map.origOps = ops;
    mapDiv.map.addIcon('restaurant', {name: 'Restaurant'});
    mapDiv.map.addIcon('attraction', {name: 'ThingToDo'});
    mapDiv.map.addEvent("noPins", function() {errorDiv.show();});
    mapDiv.map.addEvent("allPins", function() {errorDiv.hide();});
    mapDiv.map.addEvent("homePinOnly", function() {errorDiv.show();});
    mapDiv.map.addEvent("onReset", requestReset.bind(mapDiv.map, ['reset']));
    
    // recenter the map when user clicks on hotel name
    mapDiv.recenter.addEvent('click', mapDiv.map.reset.bind(mapDiv.map));
    
 // request nearby locations
    requestReset.apply(mapDiv.map, ['displayNoLog',resetComplete.bind(mapDiv.map)]);
  }
};

var resetComplete = function(t) {
  updateMarkers.apply(this,[t]);
  $('VR_MAP').getElement('.loading').hide();
  if($('VR_LOC_BOX')) $('VR_LOC_BOX').show();    
}

//A Rectangle is a simple overlay that outlines a lat/lng bounds on the
//map. It has a border of the given weight and color and can optionally
//have a semi-transparent background color.
//Adapted from http://code.google.com/apis/maps/documentation/overlays.html
var Rectangle = function(bounds, opt_weight, opt_color) {
  this.bounds_ = bounds;
  this.weight_ = opt_weight || 2;
  this.color_ = opt_color || "#888888";
}

var setupRecOverlay = function() {
  Rectangle.prototype = new GOverlay();

  //Creates the DIV representing this rectangle.
  Rectangle.prototype.initialize = function(map) {
    //Create the DIV representing our rectangle
    var div = new Element('div', {'id': 'VR_LOC_BOX', 'styles': {'border': this.weight_ + "px solid " + this.color_, 'position': 'absolute', 'display': 'none'}});
    
    //Our rectangle is flat against the map, so we add our selves to the
    //MAP_PANE pane, which is at the same z-index as the map itself (i.e.,
    //below the marker shadows)
    map.getPane(G_MAP_MAP_PANE).appendChild(div);

    this.map_ = map;
    this.div_ = div;
  }

  //Remove the main DIV from the map pane
  Rectangle.prototype.remove = function() {
    this.div_.parentNode.removeChild(this.div_);
  }

  //Copy our data to a new Rectangle
  Rectangle.prototype.copy = function() {
    return new Rectangle(this.bounds_, this.weight_, this.color_,
        this.backgroundColor_, this.opacity_);
  }

  //Redraw the rectangle based on the current projection and zoom level
  Rectangle.prototype.redraw = function(force) {
    //We only need to redraw if the coordinate system has changed
    if (!force) return;

    //Calculate the DIV coordinates of two opposite corners of our bounds to
    //get the size and position of our rectangle
    var c1 = this.map_.fromLatLngToDivPixel(this.bounds_.getSouthWest());
    var c2 = this.map_.fromLatLngToDivPixel(this.bounds_.getNorthEast());

    //Now position our DIV based on the DIV coordinates of our bounds
    this.div_.setStyles({
      width: Math.abs(c2.x - c1.x) + "px",
      height: Math.abs(c2.y - c1.y) + "px",
      left: (Math.min(c2.x, c1.x) - this.weight_) + "px",
      top: (Math.min(c2.y, c1.y) - this.weight_) + "px"
    });
  }  
};

/*
 * Create flyout for "Contact for Availability" help icon
 */ 
rules['#ACCOM_OVERVIEW .generalDetails .contactForAvail'] = function(element, event) {deferFlyout(element, {offsets:{x:0, y:12}, centerArrow:true}, 'click');};

/*
 * Update the rate drop down for the accommodation overview
 */
function updateOverviewRate()
{
  //  get the selected periodicity
  var pricePeriodWeekly = $('pricePeriod_w');
  var periodicity = pricePeriodWeekly && pricePeriodWeekly.checked ? 'w' : 'd';

  //  get the selected currency
  var l1currency = $("l1currency");
  var currency = l1currency ? l1currency.value : 0; // default to USD

  //  grab the price select and empty all children except the 'Any' option
  var select = $('l1price');
  var anyOptionLabel = select.getElement('option').innerHTML;
  select.empty();
  var anyOption = new Element('option', {'value':0});
  anyOption.innerHTML = anyOptionLabel;
  select.appendChild(anyOption);

  //  construct the map key and get the new set of options
  var key = periodicity + '_' + currency;
  var options = periodicPriceMap[key];
  for (var optionLabel in options)
  {
      var newOption = new Element('option', {'value':options[optionLabel]});
      newOption.innerHTML = optionLabel;
      select.appendChild(newOption);
  }
}

function broadenedSortChanged()
{
    var sortForm = $('SORT_FORM');
    var checked = $('BROADEN_SORT_FORM').sortGroup.checked;
    sortForm.sortOrder.value = _newBroadenedSortValue(checked, sortForm.sortOrder.value);
}

var nonHacBroadenSortChanged = function() {
    broadenedSortChanged();
    $('SORT_FORM').submit();
}

var hacBroadenSortChanged = function() {
    broadenedSortChanged();
    hacSortChanged();
}

var _newBroadenedSortValue = function(checked, val)
{
    var tmp = val;
    tmp = tmp.replace(/ab_/,"");

    // note truth change here -- we set the sort group to signify that we want a mix
    if (!checked) {
        tmp = "ab_" + tmp;
    }
    return tmp;
}

window.addEvent(TAReadyEvent, function(e)
{
    if (window.location.href.indexOf('VacationRentalReview') == -1)
    {
        return;
    }
    
    var hash = window.location.hash;

    //  if we just marked a review as helpful, expand the inline review and provide a visual
    //  indication to the user 
    if (hash.indexOf('helpful') != -1)
    {
        var reviewId = hash.replace('#helpful','');
        var moreLink = $$('.id_review_' + reviewId)[0];
        var element = $('review_' + reviewId);
        element.addEvent('onContentReplaced', function()
        {
            var elmt = $('helpful'+reviewId);
            elmt.innerHTML = '<p><font face="arial,helvetica" color="#cc0000" style="font-size: 11px">' + JS_Thankyou + '</font></p>';
            elmt.focus();
        });
        replaceContent(e, element, moreLink.href);
    }
});

function popInquiry(locationId, pid) {
  
  if (pid === undefined)
  {
    pid = -1;
  }
    
  new Ajax("/VacationRentalsAjax", 
  {
    onComplete: function(e) { ta.overlays.showInLightbox(e,{style:'og s4 VacationRentalsInquiry'}); },
    onFailure: function(e) { popErrorInquiry(locationId) },
    data: {'a':'GET_INQUIRY', 'locationId':locationId, 'pid':pid},
    evalScripts: true
  }).request();
}
 
function submitInquiry(locationId) {
  var lb = ta.retrieve('overlays.current');
  $('vrRentalInquiryButton').hide();
  $('vrRentalInquiryProgress').show();
    
  new Ajax("/VacationRentalsAjax", 
  {
    onComplete: function(e) { lb.hide(); ta.overlays.showInLightbox(e,{style:'og s4 VacationRentalsInquiry'}); },
    onFailure: function(e) { popErrorInquiry(locationId); },
    data: $('vrRentalInquiryForm'),
    evalScripts: true
  }).request();
}

function popErrorInquiry(locationId) {
  var lb = ta.retrieve('overlays.current');
  
  new Ajax("/VacationRentalsAjax", 
  {
    onComplete: function(e) { lb.hide(); ta.overlays.showInLightbox(e,{style:'og s4 VacationRentalsInquiry'}); },
    onFailure: function(e) { lb.hide(); },
    data: {'a':'ERROR_INQUIRY', 'locationId':locationId},
    evalScripts: true
  }).request();
}