var renderers = {};
Ext.ns("SABS");

// if not in here, it should be assumed, the place is in the UK
SABS.AIRPORT_TO_COUNTRY = {
    'DUB': 'IRL',
    'ORK': 'IRL',
    'SNN': 'IRL',
    'NOC': 'IRL'
};

// if not in here, assume its sterling
SABS.COUNTRY_TO_CURRENCY = {
    'IRL': 'EUR',
    'GB' : 'GBP'
};

SABS.CURRENCY_TO_UNICODE = {
    'EUR' : '\u20AC',
    'GBP' : '\u00a3'
};

/**
 * Format a number as a currency
 * @param {Number/String} value The numeric value to format
 * @param {String} value unicode currency code
 * @return {String} The formatted currency string
 */
Ext.util.Format.Money = function(v,code){
    v = (Math.round((v-0)*100))/100;
    v = (v == Math.floor(v)) ? v + ".00" : ((v*10 == Math.floor(v*10)) ? v + "0" : v);
    v = String(v);
    var ps = v.split('.');
    var whole = ps[0];
    var sub = ps[1] ? '.'+ ps[1] : '.00';
    var r = /(\d+)(\d{3})/;
    while (r.test(whole)) {
        whole = whole.replace(r, '$1' + ',' + '$2');
    }
    return code + whole + sub ;
};

/**
 * Format a number as UK currency
 * @param {Number/String} value The numeric value to format
 * @return {String} The formatted currency string
 */
Ext.util.Format.ukMoney = function(v){
    return Ext.util.Format.Money(v, '\u00a3');
};

/**
 * Format a number as Euro currency
 * @param {Number/String} value The numeric value to format
 * @return {String} The formatted currency string
 */
Ext.util.Format.euMoney = function(v){
    return Ext.util.Format.Money(v, '\u20ac');
};

SABS.Money = function(v, code){
    // neither real symbol passed as utf code or html entity 
    //if(!code)   code = 'GBP';
    if(!code){
        throw "currency code unknown for amount " + Number(v).toFixed(2);
    }
    if ( code.length > 1 ||
         (code[0] != '&' && code[code.length-1] != ';') )
    {
        if ( SABS.CURRENCY_TO_UNICODE[code] ) {
            code = SABS.CURRENCY_TO_UNICODE[code];
        }
    }
    return Ext.util.Format.Money(v, code);
};

// static methods (renderers)

/**
 * Formats the star rating
 * @param {Number} v value (integer 1-5)
 */
renderers.formatRating = function(v)
{
    return v ? String.format('<span class="x-sabs-cell-rating">{0}</span>', v) : 'N/A';
};
/**
 * Formats price to display adult/children
 * @param {Number} v value
 * @param {String} p class for the cell
 * @param {Ext.data.Record} r the whole record
 */
renderers.formatPrice = function(v, p, r) {
    // According to discussion: indication of pricing rules is even more important
    // than indication of net prices
    // therefore pricing rules icon is hereby reinstated
    if(r.json.Icons){
        if(r.json.Icons.indexOf('f') >= 0){
            p.css += 'x-grid-atol-bound-rule';
        }
        else if(r.json.Icons.indexOf('d') >= 0){
           p.css += ' x-pricing-rule';
        }
        else if (r.json.Icons.indexOf('n') >= 0){
           p.css += ' x-net-price';
        }
    }

    var curr = r.data.Currency;
    var txt = '<b>'+SABS.Money(v, curr)+'</b>';
    var sro = {Adults: 1, Children: 0, Infants: 0};//SABS.Application.getSearchModeManager().searchRequestObj;
    var rt = r.data.RecType;
    var journeyType = rt == 'HOL' || rt == 'JNY' || rt == 'FLT';
    if (journeyType && (Number(sro.Adults) > 1 || sro.Children || sro.Infants)){
        txt += '<br/>(' + SABS.Money(r.data.ADTPrice, curr) + ' pp)';
        var chd = ((Number(sro.Children) || 0) + (Number(sro.Infants) || 0));
        if(chd && r.data.CHDPrice && r.data.CHDPrice != r.data.ADTPrice) {
            txt += '<br/>(' + SABS.Money(r.data.CHDPrice || r.data.ADTPrice, curr) + ' pc)';
        }
    }
    return txt;
};
/**
 * Formats operator to show its logo
 * @param {String} v value
 * @param {String} p class for the cell
 * @param {Ext.data.Record} r the whole record
 */
renderers.formatOperator = function (v, p, r) {   
    if(r.data.Operator == 'ADH'){
       return '<img src="http://images.ramesystravel.co.uk/images/sabs2/ADV-ATOL.jpg" width="36" height="36" alt="Advantage ATOL" class="operator-logo"/>';
    }
    return String.format('<img src="{0}{1}.gif" width="88" height="22" alt="{2} ({1})" title="{2} ({1})" class="operator-logo"/>', OPERATOR_LOGO_PATH, r.data.Operator, r.data.OperatorFull, v);
};
/**
 * Formats date like: Tue, 12 March 2007 12:30 pm
 * @param {Date} v date object
 */
renderers.formatDate = function(v) {
    return v ? v.dateFormat('D, j M Y') : '';
};
/**
 * Formats date like: Tue, 12 March 2007 12:30 pm
 * @param {Date} v date object
 */
renderers.formatDateTime = function(v, p, r) {
    if(v) {
        return v.dateFormat('D, j M Y') + '<br/>' + v.dateFormat('H:i');
    }
    return 'N/A';
};
/**
 * Formats date like: Tue, 12 March 2007 12:30 pm
 * @param {Date} v date object
 */
renderers.formatFlightDepartureDateTime = function(v, p, r) {
    
    // scheduled
    if(Number(r.data.LegsCount)){
        var d = r.data;
        return [
           (d.DepDateOut || v).dateFormat('D, j M Y'),
           '<br/>',
           'Dep: ',
           (d.DepTimeOut || v).dateFormat('H:i'),
           ', Arr: ',
           (d.ArrTimeOut ? d.ArrTimeOut.dateFormat('H:i') : 'N/A')
        ].join('');
    }
    // fallback
    return renderers.formatDateTime(v,p,r);
};
renderers.formatFlightReturnDateTime = function(v, p, r) {
    
    // scheduled
    if(Number(r.data.LegsCount)){
        var d = r.data;
        return [
           (d.DepDateIn || v).dateFormat('D, j M Y'),
           '<br/>',
           'Dep: ',
           (d.DepTimeIn || v).dateFormat('H:i'),
           ', Arr: ',
           (d.ArrTimeIn ? d.ArrTimeIn.dateFormat('H:i') : 'N/A')
        ].join('');
    }
    // fallback
    return renderers.formatDateTime(v,p,r);
};
/**
 * Formats departure points
 * @param {String} v departure city, region (or country)
 * @param {String} p class for the cell
 * @param {Ext.data.Record} r the whole record
 */
renderers.formatDeparturePoint = function(v, p, r) {
    return String.format('{0}<br/><span class="auxiliary">{1}</span>', v.split(",")[0], r.data.DepPointOut);
};
/**
 * Formats destination points
 * @param {String} v destination city, region (or country)
 * @param {String} p class for the cell
 * @param {Ext.data.Record} r the whole record
 */
renderers.formatDestinationPoint = function(v, p, r) {
    return String.format('{0}<br/><span class="auxiliary">{1}</span>', v.split(",")[0], r.data.ArrPointOut);
};
/**
 * Creates action buttons
 */
renderers.formatActions = function(v, p, r) {
    p.css = "x-grid-action-cell";
    var dbvs = r.data.BookableViaSabs;
    var basket = '<a qtip="Add to basket" href="#{0}" class="x-grid-action-button x-grid-action-basket"></a>';
    var xpress = '<a qtip="Express checkout" href="#{0}" class="x-grid-action-button x-grid-action-expresscheckout' + (dbvs ? '' : ' x-grid-action-inactive') + '"></a>';
    var register;
    if((r.json.Icons || '').indexOf('r') >= 0){
        var opname = r.data.OperatorFull || r.data.Operator;
        register = '<span qtip="Register at ' + opname + ' in order to book this item" class="x-grid-action-button x-grid-action-register"></span>';
    }
    else {
        register = '';
    }
    var direct;
    var icons = r.json.Icons || '';
    if (icons.indexOf('v') >= 0)
	 {

        if (r.data.LatesFlag && r.data.LatesFlag=='L')
		  {
           direct = '<a qtip="Book Late in Viewdata" href="#{0}" class="x-grid-action-button x-grid-action-late_viewdata"></a>';
		  }else
		  {
           direct = '<a qtip="Find in Viewdata" href="#{0}" class="x-grid-action-button x-grid-action-viewdata"></a>';
		  }
	 }
    else if (r.json.WebLink && icons.indexOf('w') >= 0) {
        direct = '<a qtip="Weblink" href="' + r.json.WebLink + '" class="x-grid-action-button x-grid-action-weblink"></a>';
    }
    else {
        direct = '';
    }
    var txt = [basket, xpress, direct, register].join('');
    return String.format(txt, r.id);
};
 /**
  * Makes sure names arent all-caps
  */
renderers.formatAccommodation = function(v) {
    return v;
    //return Ext.util.Format.wordCapitalize(v);
};
/**
 * Shows record type icon
 */
renderers.formatRecordType = function(v, p, r) {
    var img, alt, cls, type;
    switch (v) {
        case 'HOL':
            img = 'hol';
            cls =  'x-grid-recordtype-holiday';
            alt = 'Holiday';
            type = 'holiday';
            break;
        case 'ACC':
            img = 'acc';
            cls =  'x-grid-recordtype-accomm';
            alt = 'Accommodation';
            type = 'accommodation';
            break;
        case 'JNY':
        case 'FLT':
            img = 'flt';
            cls =  'x-grid-recordtype-flight';
            alt = 'Flight';
            type = 'flight';
            break;
        case 'PKG':
            img = 'hol';
            cls =  'x-grid-recordtype-holiday';
            alt = 'Fare';
            type = 'fare';
            break;
        case 'TRS':
            if(r.data.ProductType == 'Car Hire') {
                img = 'car';
                cls =  'x-grid-recordtype-carhire';
                alt = 'Car&nbsp;hire';
                type = 'carhire';
            }
            else {
                img = 'trs';
                cls =  'x-grid-recordtype-transfer';
                alt = 'Transfer';
                type = 'transfer';
            }
            break;
    }
    p.css = cls;
    var direct;
    var bookingOrBooked = (r.data.Booking == 'Booking item' || r.data.Booking.indexOf('successful') >= 0 || r.data.BKG.length);
    var _active =  bookingOrBooked ? ' x-grid-action-inactive' : ' ';
    var dlete = '';
    if(type != 'fare'){
        dlete = '<a qtip="Delete" href="#{0}" class="x-grid-action-button x-grid-action-delete'+ _active + '"></a>';
    }

    if ((r.json.Icons || '').indexOf('v') >= 0)
	 {
        if (r.data.LatesFlag && r.data.LatesFlag=='L')
		  {
           direct = '<a qtip="Book Late in Viewdata" href="#{0}" class="x-grid-action-button x-grid-action-late_viewdata"></a>';
		  }else
		  {
           direct = '<a qtip="Find in Viewdata" href="#{0}" class="x-grid-action-button x-grid-action-viewdata"></a>';
	     }	 
	 }
    else if ((r.json.Icons || '').indexOf('w') >= 0) {
        direct = '<a qtip="Weblink" href="' + r.json.WebLink + '" class="x-grid-action-button x-grid-action-weblink"></a>';
    }

    var match = '';
    if (v == 'HOL' || v == 'JNY') {
        var inactive = (r.data.ExpressBooking || bookingOrBooked) ? ' x-grid-action-inactive' : '';
        match = '<a qtip="Search for matching items" href="#{0}" class="x-grid-action-button x-grid-action-addmatching' + inactive + '"></a>';
    }
    var txt = [dlete, direct, match].join('');
    var rv =  String.format('<div style="height:32px; line-height:26px; padding-left:26px;">{0}</div>', alt);

    return rv + String.format(txt, r.id);
};
renderers.formatBasketJourney = function(v, p, r) {
    var rt = r.data.RecType;
    if(rt == 'PKG'){
        return r.data.Description;
    }
    else if(rt == 'ACC' || rt == 'PKG') {
        p.css = 'x-grid-notappliccable';
        return 'n/a';
    }
    else if (rt == 'TRS') {
        if(r.data.ProductType == 'Car Hire') {
            return String.format( '<b>Car type: </b>{0},<br/>{1},<br/>{2},<br/>{3}',
                r.data.Model, 'Doors: ' + r.data.Doors ? r.data.Doors : 'no information',
                r.data.Auto ? 'Automatic' : 'Manual',
                r.data.Aircon ? 'Air conditioning' : '');
        }
        else {
            return String.format('From airport to hotel<br/><b>Time: </b>{0} minutes<br/><b>Car type: </b>{1}',
                r.data.TransferMinutes, r.data.ProductType);
        }
    }
    else {
        if(Number(r.data.LegsCount)){
            var d = r.data;
            return String.format('<b>Out: </b>{0}&nbsp;&rsaquo;&nbsp;{1}<br/>Dep: {2} {3},<br/>Arr: {4} {5}<br/><b>In: </b>{1}&nbsp;&rsaquo;&nbsp;{0}<br/>Dep: {6} {7},<br/>Arr: {8} {9}',
                d.DepPointOut, d.ArrPointOut,//0,1
                Ext.isDate(d.DepDateOut) ? d.DepDateOut.dateFormat('D, j M Y'):'',//2
                Ext.isDate(d.DepTimeOut) ? d.DepTimeOut.dateFormat('H:i'):'',//3
                Ext.isDate(d.ArrDateOut) ? d.ArrDateOut.dateFormat('D, j M Y'):'',//4
                Ext.isDate(d.ArrTimeOut) ? d.ArrTimeOut.dateFormat('H:i'):'',//5

                Ext.isDate(d.DepDateIn) ? d.DepDateIn.dateFormat('D, j M Y'):'',//6
                Ext.isDate(d.DepTimeIn) ? d.DepTimeIn.dateFormat('H:i'):'',//7
                Ext.isDate(d.ArrDateIn) ? d.ArrDateIn.dateFormat('D, j M Y'):'',//6
                Ext.isDate(d.ArrTimeIn) ? d.ArrTimeIn.dateFormat('H:i'):''); //9
        }

        return String.format('<b>Out: </b>{0}&nbsp;&rsaquo;&nbsp;{1}<br/>{2}<br/><b>In: </b>{1}&nbsp;&rsaquo;&nbsp;{0}<br/>{3}',
            r.data.DepPointOut, r.data.ArrPointOut,
            r.data.DepartureDateTime ? r.data.DepartureDateTime.dateFormat('D, j M Y, H:i') : '&mdash;',
            r.data.ReturnDateTime ? r.data.ReturnDateTime.dateFormat('D, j M Y, H:i') : '&mdash;');
    }
};

renderers.formatBasketAccommodation = function(v, p, r) {
    if(r.data.RecType != 'ACC' && r.data.RecType != 'HOL') {
        p.css = 'x-grid-notappliccable';
        return 'n/a';
    }
    else {
        var rv = String.format(
            '<b>{0}</b><br/>',
            r.data.HotelName);

        var detail = '';
        if ( r.data.BoardBasis != 'AN' ) {
            detail += 'Board basis: ' + r.data.BoardBasis;
        }
        if ( r.data.Rating ) {
            if ( detail ) {
                detail += ', ';
            }
            detail += 'Rating: <span class="x-sabs-cell-rating">' + r.data.Rating + '</span>';
        }

        if ( detail ) {
            rv += detail + '<br/>';
        }

        rv += r.data.ResortName;

        if(r.data.RecType == 'ACC') {

            var pr = r.data.PickedRoom;
            if(typeof pr == 'String'){
                rv += '<br/>' + r.data.PickedRoom;
            }
            else if(Ext.isArray(pr)){
                for(var i=0, prl=pr.length; i<prl; i++) {
                    rv += '<br/>' + pr[i].RoomDesc + ', ' + pr[i].RateDescription;
                }
            }

            rv += String.format('<br/><b>Check-in: </b>{0};<br/><b>Check-out: </b>{1}',
                r.data.CheckInDate ? r.data.CheckInDate.dateFormat('D, j M Y') : '&mdash;',
                r.data.CheckOutDate ? r.data.CheckOutDate.dateFormat('D, j M Y') : '&mdash;');
        }
        return rv;
    }
};

renderers.formatBookingPhase = function(v, p, r) {
    var txt;
    if(r.data.BookableViaSabs !== true) {
        var direct; 

        if(!r.json.Icons) {
            return '';
        }

        if (r.json.Icons.indexOf('v') >= 0) {
            direct = '<a qtip="Book in Viewdata" href="#' + r.id + '" class="x-grid-action-booking x-grid-action-viewdata">Book here</a>';
        }
        else if (r.json.Icons.indexOf('w') >= 0) {
            direct = '<a qtip="Book through website" href="' + r.json.WebLink + '" class="x-grid-action-booking x-grid-action-weblink">Book here</a>';
        }

        return [ '<p style="margin-top:10px; text-align:center">',
            '<img src="http://images.ramesystravel.co.uk/images/sabs2/icons/error.png" /><br/>',
            '<b>Not directly bookable via SABS</b></p>',
            '<p style="text-align:center">', direct, '</p>' ].join('');
    }
    else if(v.indexOf('Booking successful') === 0) {
        txt = v;
        if(r.data.BKG) {
            txt += '<br/><b>Op.Ref: ' + r.data.BKG[0].BookingRef + '</b>';
        }

        return '<p style="margin-top:10px; text-align:center">' + txt + '</p>';
    }
    else if(r.data.BKG instanceof Array) { // this happens on load of a booked item???
        var bkg = r.data.BKG;
        var bdate = Date.parseDate(bkg[0].ScanDate + bkg[0].ScanTime, 'y/m/dHi');
        var t = 'Booked at ' + bdate.format('d/m/y H:i') + '<br/><b>Op.Ref: ' + bkg[0].BookingRef + '</b>';
        return '<p style="margin-top:10px; text-align:center">' + t + '</p>';
    }
    else if (v === '' || v === null || v === false || v === true) {
        if(r.json.Source == 'R' && r.json.ScanDate && r.json.ScanTime) {
            var d = Date.parseDate(r.json.ScanDate + r.json.ScanTime, 'ymdHi');
            d = d.format('D, j M Y H:i');
            return '<p style="margin-top:10px; text-align:center"><img src="http://images.ramesystravel.co.uk/images/sabs2/icons/help.png" /><br/>Availability confirmed at<br/>' + d +'</p>';
        }
        else {
            return '<p style="margin-top:10px; text-align:center"><img src="http://images.ramesystravel.co.uk/images/sabs2/icons/help.png" /><br/>Availability unconfirmed</p>';
        }
    }
    else if(v == 'Confirming availability' || v == 'Confirming price' || v == 'Booking item' || v == 'Quick costing') {
        return '<p style="margin-top:10px; text-align:center"><img src="http://images.ramesystravel.co.uk/images/sabs2/loading-white.gif" /><br/>' + v + '</p>';
    }
    else if(v == 'Booking error' || v == 'Booking cancelled' || v == 'Connection error' || v=='No alternatives available') {
        return '<p style="margin-top:10px; text-align:center"><img src="http://images.ramesystravel.co.uk/images/sabs2/icons/exclamation.png" /><br/>' + v + '</p>';
    }
    else if(v == 'Ready to book')
    {
        return '<p style="margin-top:10px; text-align:center"><a class="fake-button booking-btn" href="#"><span>Book this item</span></a><p>';
    }
    else if(v == 'Pick alternative'){
        return '<p style="margin-top:10px; text-align:center">Item not available</p><p style="margin-top:3px; text-align:center"><a href="#" class="fake-button x-grid-basket-alternatives"><span>'+ v +'</span></a></p>';
    }
    else if(v == 'Availability confirmed'){// || v == 'Room allocation'){
        //var txt = '<p style="margin-top:10px; text-align:center"><img src="http://images.ramesystravel.co.uk/images/sabs2/icons/hourglass.png" /><br/>' + v;
        //txt +='<a href="#" class="fake-button x-grid-basket-show-form"><span>Show form</span></a></p>';
        txt ='<p style="text-align:center"><a href="#" class="fake-button x-grid-basket-quick-costing"><span>Do quick costing</span></a></p>';
        txt +='<p style="margin-top:3px; text-align:center"><a href="#" class="fake-button x-grid-basket-continue-booking"><span>Continue booking</span></a></p>';
        return txt;
    }
    else if(v == 'Quick Costing done'){
        txt = '<p style="margin-top:10px; text-align:center">' + v + '</p>';
        txt +='<p style="margin-top:5px; text-align:center"><a href="#" class="fake-button x-grid-basket-continue-booking"><span>Continue booking</span></a></p>';
        return txt;
    }
    else if(v == 'Input required'){
        txt = '<p style="margin-top:10px; text-align:center">' + v + '</p>';
        txt +='<p style="margin-top:5px; text-align:center"><a href="#" class="fake-button x-grid-basket-show-form"><span>Show form</span></a></p>';
        return txt;
    }
    else {
        return '<p style="margin-top:10px; text-align:center"><img src="http://images.ramesystravel.co.uk/images/sabs2/icons/hourglass.png" /><br/>' + v + '</p>';
    }
};
renderers.formatBoolean = function(v) {
    return v ? 'Yes':'No';
};
renderers.formatLuggage = function(v, p, r) {
    return String.format('small: {0} pcs,<br/>large: {1} pcs', r.data.SmlLuggage, r.data.LrgLuggage);
};

renderers.formatBasketPrice = function(v, p, r, ri, ci, store) {
    if((r.json.Icons || '').indexOf('f') >= 0){
        p.css += 'x-grid-atol-bound-rule';
    }
    var str = '', old=false;
    if(r.data.QTE && r.data.QTE[0] && r.data.QTE[0].TotalCost){
        str = 'confirmed <b>' + SABS.Money(r.data.QTE[0].TotalCost, r.data.Currency) + '</b><br/>';
        old = true;
    }

    if(r.data.ReturnPrice) {
        p = SABS.Money(r.data.ReturnPrice, r.data.Currency);
    }
    else if(r.data.SinglePrice) {
        p = SABS.Money(r.data.SinglePrice, r.data.Currency);
    }
    else {
        //if (r.json.Icons.indexOf('d') >= 0)     p.css += ' x-pricing-rule';
        p = SABS.Money(v, r.data.Currency);
    }
    if(r.data.RecType == 'PKG'){
        SABS.Money(v, r.data.Currency);
    }

    if(r.data.QTE && r.data.QTE[0] && r.data.RecType != 'PKG'){
        var nprice = Number(r.data.QTE[0].TotalCost);
        str = 'confirmed <b>' + SABS.Money(nprice, r.data.Currency) + '</b><br/>';
        old = true;
    }
    str += old ? '<span style="text-decoration:line-through">' : '<span style="font-weight:bold">';
    //str +=  SABS.Money(p, r.data.Currency);
    str += p+'</span>';

    var pp = '';
    var sro = renderers.findPaxDataSource(v,p,r,ri,ci,store);
    var rt = r.data.RecType;
    if (!old && (rt == 'HOL' || rt == 'JNY' || rt == 'FLT') &&
       (Number(sro.Adults) > 1 || sro.Children || sro.Infants)){
        pp += '<br/>(' + SABS.Money(r.data.ADTPrice, r.data.Currency) + ' pp)';
        var chd = ((Number(sro.Children) || 0) + (Number(sro.Infants) || 0));
        if(chd && r.data.CHDPrice && r.data.CHDPrice != r.data.ADTPrice) {
            pp += '<br/>(' + SABS.Money(r.data.CHDPrice || r.data.ADTPrice, r.data.Currency) + ' pc)';
        }
    }
    str += pp;
    return str;
};
renderers.findPaxDataSource = function(v,p,r,ri,ci, store){
    if('Adults' in r.data && 'Children' in r.data && 'Infants' in r.data){
        return r.data;
    }
    if(store.searchRequestObj){
        return store.searchRequestObj;
    }
    if(Ext.isArray(r.data.PAS)){
        return r.data.PAS[0];
    }
};
renderers.bookableGroupRenderer = function(v){
    return v ? 'Bookable through SABS' : 'Not bookable through SABS';
};


renderers.formatRoomChoice = function(v,m,r){
    var rm = r.data.AvRooms;
    // get rid of empty string at the end of array
    if(rm.length && rm[rm.length-1].length === 0) {
        rm.pop();
    }
    var rml = rm.length;
    if(rml > 1) {
        m.css = 'x-grid-room-choice-avail';
    }
    if(v) {
        return v;
    }

    var rma;
    var adtp = r.data.ADTPrice;
    for(var i=0; i<rml; i++){
        rma =rm[i].split(',');
        if(Number(rma[5]) == adtp) {
            return rma[1];
        }
    }
};

renderers.formatMinutes = function(v)
{
    if ( v ) {
       return v == 1 ? v + ' minute' : v + ' minutes';
    }
    else {
       return 'N/A';
    }
};
