//------------------------------------------------------------------------------
/** A collection of general useful functions.
 */


//= namespace ft
if (typeof(ft) == 'undefined') ft = {};

ft.Utils = {};
   

//------------------------------------------------------------------------------
/** Checks if running under any version of IE
 */
ft.Utils.IsIE = function()
{
    return navigator.appName == "Microsoft Internet Explorer";
};

//------------------------------------------------------------------------------
/** Checks if running under IE version 6.x or earlier
 */
ft.Utils.IsIE6 = function()
{
    return ft.Utils.IsIE() &&
            Number(navigator.userAgent.match(/MSIE (.{1,2})\./)[1]) < 7 ;
};

//------------------------------------------------------------------------------
/** Checks if running under IE version 7.x or earlier
 */
ft.Utils.IsIE7 = function()
{
    return ft.Utils.IsIE() &&
            Number(navigator.userAgent.match(/MSIE (.{1,2})\./)[1]) < 8 ;
};

//------------------------------------------------------------------------------
/** Checks if running under IE version 8.x or earlier
 */
ft.Utils.IsIE8 = function()
{
    return ft.Utils.IsIE() &&
            Number(navigator.userAgent.match(/MSIE (.{1,2})\./)[1]) < 9 ;
};

//------------------------------------------------------------------------------
/** Recursively produces a string representing an HTML element tree hierarchy.
 */
ft.Utils.deepInspect = function(elem, indent)
{
    var out = elem.inspect();    
    if (!indent)
        indent = "  ";
    elem.childElements().each(function(child) {
        out += "\n" + indent + deepInspect(child, indent+"  ");
    });
    return out;
};

//------------------------------------------------------------------------------
/* Checks if the given string represents a valid phone number.
 * Accepted formats:
 *  full international number: +1-626-555-1234 (suports any country code as long
 *                             as it's prceded by a + and followed by a non-digit)
 *  US number : 626-555-1234  (if no +cc is present, it's assumed to be a US
 *                             10-digit phone number)
 *  In general, non-digit characters, other than those for the country code,
 *  are ignored.
 */
ft.Utils.isValidPhone = function(phone)
{
    phone = phone.strip();
    var m = phone.match(/^\+(\d{1,3})\D(.+)$/);
    if (m && m[2].replace(/\D/g, '').length >= 2)
        return true;
    return phone.replace(/\D/g, '').match(/^\d{10}$/);
};

//------------------------------------------------------------------------------
/* Formats a phone number string in a normalized way. It removes any non-digit
 * characters and returns either a raw number (with country code and no
 * separators), or a pretty format (with +cc-xxx-xxx-xxxx form).
 *
 * @param phone string with phone number, with our without separators
 * @param raw true to return the plain number (just digits), false to return
 *      the formatted number with proper separators (i.e. "pretty")
 */
ft.Utils.formatPhone = function(phone, raw)
{
    // Remove any surrounding whitespace 
    phone = phone.strip();
    // First check if phone matches a pattern like +cc-*, where cc represents
    // a country code of 1 to 3 digits, followed by a non-digit and then
    // any number of digits and separators.
    var m = phone.match(/^\+(\d{1,3})\D(.+)$/);
    // If so, extract the country code, assume the rest is the local
    // phone number and leave only digits in it, then format it.
    if (m)
    {
        var cc = m[1];
        var cn = m[2].replace(/\D/g, '');
        return raw? cc + cn : '+' + cc + '-' + ft.Utils.formatLocalPhone(cn);
    }
    // Otherwise, see if, after removing separators, it corresponds to a
    // 0-to-3 digit string followed by a 10-digit string, and if so format it as
    // the 10 digits as a local number and the prefix as country code, using
    // an assumed country code of 1 (US) if none found.
    m = phone.replace(/\D/g, '').match(/^(\d{0,3})(\d{10})$/);
    if (m)
    {
        var cc = m[1]==''? '1' : m[1];
        var cn = m[2];
        
        return raw? cc + cn : '+' + cc + '-' + ft.Utils.formatLocalPhone(cn);
    }
    // Otherwise, return the string unchanged.
    return phone;
};

//------------------------------------------------------------------------------
/* Formats a local (i.e. without contry code) phone number. It produces a string
 * of the form xxx-xxx-xxxx.
 */
ft.Utils.formatLocalPhone = function(phone)
{
    var out = '';
    var groupLen = 4;
    var count = 0;
    for (var k=phone.length-1; k>=0; --k)
    {
        if (count++ == groupLen)
        {
            out = '-' + out;
            count = 1;
            groupLen = 3;
        }
        out = phone.charAt(k) + out;
    }
    return out;
};

//------------------------------------------------------------------------------
/* Checks if given string looks like a valid email
 */
ft.Utils.isValidEmail = function(email)
{
    return /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,6}$/i.test(email.strip());
};

//------------------------------------------------------------------------------
/* Normalizes an email address, convertitng to lowercase, and stripping any
 * surrounding spaces.
 */
ft.Utils.normalizeEmail = function(email)
{
    return email.strip().toLowerCase();
};

//------------------------------------------------------------------------------
/** Checks whether the given string is a valid 5-digit or 5+4 digit US zip code
 * (just valid format, not values)
 */
ft.Utils.isValidZip = function(zip)
{
    return /^\d{5}(-\d{4})?$/.test(zip.strip());
};

//--------------------------------------------------------------------------
/* Formats an integer number to use comma separators every 3 digis.
 */
ft.Utils.formatInteger = function(n)
{
    var out ='';
    var count = 0;
    while (n > 0)
    {
        var digit = n%10;
        if (count > 0 && count % 3 == 0)
            out = digit + ',' + out;
        else
            out = digit + out;
        n = (n-digit)/10;
        ++count;
    }
    return out;
};

//------------------------------------------------------------------------------
/* Given a number, possibliy with decimals (cents), format it as a dollars and
 * cents string, e.g., 10 -> "$10.00", 19.9 -> "$19.90", etc.
 * It also rounds up the number, to ensure you get a whole number of cents.
 */
ft.Utils.formatCurrency = function(amount)
{
    var cents = Math.round(amount*100);
    var out = "$" + (cents/100);
    if (cents % 100 == 0)
        out += '.00';
    else if (cents % 10 == 0)
        out += '0';
    return out;
};

//--------------------------------------------------------------------------
/** Formats a Date object into a short date
 */
ft.Utils.formatDate = function(d, asUtc)
{
    var y, m, day;
    if (asUtc)
    {
        y = d.getUTCFullYear();
        m = d.getUTCMonth();
        day = d.getUTCDate();
    }
    else
    {
        y = d.getFullYear();
        m = d.getMonth();
        day = d.getDate();
    }
    var sm = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'][m];
    return sm + ' ' + day + ', ' + y;
}

//-----------------------------------------------------------------------------
/** Retrieves the rectangle information for the current document viewport, in a
 * cross-browser way. Prototype's document.viewport.getDimensions() doesn't work.
 *
 * @return an object with propeties: top, left, right, bottom, width, height.
 */
ft.Utils.getViewport = function()
{
    var start = document.viewport.getScrollOffsets();
    var width, height;
    if (document.body.clientWidth)
    {
        width = document.body.clientWidth;
        height = document.body.clientHeight;
    }
    else
    {
        width = window.innerWidth;
        height = window.innerHeight;
    }
    return {top:start.top, left:start.left, width:width, height:height,
             right:start.left+width, bottom:start.right+height};
};

//-----------------------------------------------------------------------------
/** Displays a translucent shield over the whole window, mainly to allow other
 * GUI elements, like dialogs, to be displayed on top of it, and at the same
 * time disable access to any elements underneath it.
 *
 * @param id  unique id to use for the shield element. Default: ft_shield.
 * @param opacity float value between 0 and 1 used to specify the translucent
 *      level of the shield. Default 0.3.
 * @param zIndex z-index to use for displaying the shield. Default: 100.
 * @return the shield element itself
 */
ft.Utils.showShield = function(id, opacity, zIndex)
{
    if (id == undefined)
        id = 'ft_shield';
    if (opacity == undefined)
        opacity = 0.3;
    if (zIndex == undefined)
        zIndex = 100;
        
    var vp = ft.Utils.getViewport();
    vp.width = document.viewport.getWidth();
    vp.height = document.viewport.getHeight();
    var shield = new Element('div', {id:id});
    shield.setStyle({position:'absolute', top: vp.top+'px', left: vp.left+'px',
                     zIndex: zIndex,
                     width: vp.width+'px', height: vp.height+'px', backgroundColor: 'black',
                     opacity: opacity});
    $(document.body).firstDescendant().insert({before: shield});
    
    // If this is IE6, hide all select elements underneath it, so they
    // won't overlap
    if (ft.Utils.IsIE6() && !ft.Utils.LeaveSelectsAlone)
    {
        var zShield = Number(shield.getStyle('zIndex'));
        $$('select').each(function(elem) {
            var z = elem.getStyle('zIndex');
            z = z==undefined? 0 : Number(z);
            if (z < zShield)
                elem.setStyle({visibility:'hidden'});
        });
    }
    return shield;
}

//--------------------------------------------------------------------------
/** Removes the shield element
 */
ft.Utils.hideShield = function(shield)
{
    if (!shield)
        return;
    
    shield.remove();
        
    // If this is IE6, redisplay all hidden select elements underneath it
    if (ft.Utils.IsIE6() && !ft.Utils.LeaveSelectsAlone)
    {
        var zShield = Number(shield.getStyle('zIndex'));
        $$('select').each(function(elem) {
            var z = elem.getStyle('zIndex');
            z = z==undefined? 0 : Number(z);
            if (z < zShield)
                elem.setStyle({visibility:'inherit'});
        });
    }
}

ft.Utils.ShieldCount = 0;

//-----------------------------------------------------------------------------
/** Displays a (formerly display:none) div as a dialog for the user to interact
 * with, on top of a translucent shield that prevents interaction with the page.
 */
ft.Utils.showDialog = function(dialogDiv, topOffset)
{
    // Display shield
    var n = ft.Utils.ShieldCount++;
    var zShield = 100 + 10*n;
    ft.Utils.showShield('dialog_shield_' + n, 0.3, zShield);
    
    // Center dialog in viewport and make it visible
    if (!topOffset)
        topOffset = 0;
    var vp = ft.Utils.getViewport();
    var vpWidth, vpHeight;
    if (window.innerWidth)
    {
        vpWidth = window.innerWidth;
        vpHeight = window.innerHeight;
    }
    else
    {
        vpWidth = vp.width;
        vpHeight = vp.height;
    }
    var ddim = dialogDiv.getDimensions();
    var dLeft = vp.left + (vpWidth - ddim.width)/2;
    var dTop = (vp.top + topOffset) + (vpHeight - ddim.height)/2 ;
    dialogDiv.setStyle({position:'absolute',
                        top:dTop+'px', left:dLeft+'px',
                        zIndex: zShield+1,
                        display:'block'});
};

//------------------------------------------------------------------------------
/** Hides the given div (assumed to contain dialog interface elements), and
 * removes the shield, if present.
 */
ft.Utils.closeDialog = function(dialogDiv)
{
    dialogDiv.setStyle({display:'none'})
    if (ft.Utils.ShieldCount > 0)
    {
        var n = --ft.Utils.ShieldCount;
        ft.Utils.hideShield($('dialog_shield_' + n));
    }
};


//------------------------------------------------------------------------------
/** Sets a Cookie with the given name and value.
 *
 * name       Name of the cookie
 * value      Value of the cookie
 * [expDays]  # of days from now for expiration date (default: end of current session)
 * [path]     Path where the cookie is valid (default: path of calling document)
 * [domain]   Domain where the cookie is valid
 *              (default: domain of calling document)
 * [secure]   Boolean value indicating if the cookie transmission requires a
 *              secure transmission
 */
ft.Utils.setCookie = function(doc, name, value, expDays, path, domain, secure) {
    var expDate = null;
    if (expDays)
    {
        expDate = new Date();
        expDate.setTime(expDate.getTime() + expDays*24*60*60*1000);
    }
    doc.cookie= name + "=" + escape(value) +
        ((expDate) ? "; expires=" + expDate.toGMTString() : "") +
        ((path) ? "; path=" + path : "") +
        ((domain) ? "; domain=" + domain : "") +
        ((secure) ? "; secure" : "");
};

//--------------------------------------------------------------------------
/** Gets the value of the specified cookie.
 *
 * @param name  Name of the desired cookie.
 *
 * @return  a string containing value of specified cookie,
 *          or null if cookie does not exist.
 */
ft.Utils.getCookie = function(doc, name) {
    var dc = doc.cookie;
    var prefix = name + "=";
    var begin = dc.indexOf("; " + prefix);
    if (begin == -1)
    {
        begin = dc.indexOf(prefix);
        if (begin != 0)
            return null;
    }
    else 
        begin += 2;
    
    var end = doc.cookie.indexOf(";", begin);
    if (end == -1) 
        end = dc.length;
    
    return unescape(dc.substring(begin + prefix.length, end));
};



