;(function($)
{
    /**
     * General utility functions
     * @id              $Id: fcl.util.js 1582 2010-09-14 03:57:09Z blundenr $
     * @author          Ryan Blunden
     * @modifiedby      $LastChangedBy: blundenr $
     * @copyright       Copyright Flight Centre Ltd. All rights reserved.
     * @version         $Revision: 1582 $
     * @lastmodified    $Date: 2010-09-14 13:57:09 +1000 (Tue, 14 Sep 2010) $
     * @requires        FCL
     * @class
     * @static
     */
    FCL.UTIL =
    {
        /**
         * Name of this class (used for error handling and/or debugging purposes)
         * @type String
         */
        name: 'FCL.UTIL',

        /**
         * Define jQuery api for some of the FCL.UTIL methods
         */
        init: function()
        {
            $.fn.fclExternalLinks = function()
            {
                return FCL.UTIL.externalLinks(this);
            };

            $.fn.fclTruncateText = function(settings)
            {
                settings = settings||{};
                return FCL.UTIL.truncateText(this, settings);
            };

            String.prototype.fclFormat = this.format;
        },

        /**
         * Launch a popup window
         * TODO Test if popup blocked
         * @param {String} url
         * @param {String} [name="popup"] Name of popup window
         * @param {Interger} [width="380"] Width of popup window
         * @param {Integer} [height="220"] Height of popup window
         */
        popup: function(url, windowName, width, height)
        {
            var name = (typeof windowName == 'undefined') ? 'popup' : windowName;
            var width = (typeof width == 'undefined') ? 380 : width;
            var height = (typeof height == 'undefined') ? 220 : height;
            var win = window.open(url, windowName, 'toolbar=no,location=no,directories=no,status=yes,menubar=no,scrollbars=yes,resizable=yes,copyhistory=no,width='+width+',height='+height);
            win.focus();
            return win;
        },

        /**
         * Validate an email address
         * @param {String} email
         * @returns {Boolean}
         */
        isEmailValid: function(email)
        {
            // By Scott Gonzalez: http://projects.scottsplayground.com/email_address_validation/
            return /^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?$/i.test(email);
        },

        /**
         * Validate an Australian post code
         * @param {String} postCode
         * @returns {Boolean}
         */
        isValidPostCode: function(postCode)
        {
            postCode = postCode.toString().match(/\d+/g)[0];
            if(postCode.length !== 4 || parseInt(postCode, 10) < 0 || parseInt(postCode, 10) >= 9000)
            {
                return false
            }

            return true;
        },

        /**
         * Return amount in Australian currency format
         * @param {Float}
         * @returns {String}
         */
        formatCurrency: function(amount)
        {
            var amount, amountArray;

            amount = amount.toString().match(/[\d\.]+/)[0];
            amountArray = [];

            // If amount has a decimal, add this to the array and extract it from amount
            if(amount.indexOf('.') > -1)
            {
                amountArray.unshift(amount.substring(amount.indexOf('.')));
                amount = amount.substring(0, amount.indexOf('.'));
            }

            // Split the amount into array elements every three numbers
            for(var i=amount.length; i>0; i-=3)
            {
                amountArray.unshift(amount.substring(i, i-3));
            }

            // Put it all back together again (replacing ,. is for numbers with decimals)
            return amountArray.join(',').replace(',.', '.');
        },

        /**
         * Return object with query string variables as object properties
         * @param {String} [strUrl=window.location.search]
         * @param {Stsring} [param="getVariable"] Use if you're only after one specific query string variable (Use FCL.UTIL.getUrlVar if this is the case)
         * @returns {Object or String} What gets returned depends on whether entire collection is returned or just one variable.
         * Returns an empty string if no query string variables are found.
         */
        getUrlVars: function(strUrl, param)
        {
            var queryString='', urlVars='', urlObj={};

            if (strUrl == '' || typeof strUrl == 'undefined')
            {
                queryString = window.location.search;
            }
            else
            {
                queryString = strUrl;
            }

            // if no URL parameters exist, return null
            if (queryString == '')
            {
                return '';
            }

            // remove '?'
            urlVars = queryString.substring(1).split('&');

            for(var i=0; i<urlVars.length; i++)
            {
                var urlVar = urlVars[i].split('=');
                urlObj[urlVar[0]] = decodeURIComponent(urlVar[1]);
            };

            if(typeof param != 'undefined')
            {
                if(typeof urlObj[param] != 'undefined')
                {
                    return urlObj[param];
                }
                else
                {
                    return '';
                }
            }

            return urlObj;
        },

        /**
         * Get a single query string variable
         * @param {String} key Query string variable name
         * @returns {String} Query string variable if exists or empty string if not
         */
        getUrlVar: function(key)
        {
            return this.getUrlVars('', key);
        },

        /**
         * Attempt to log an expression to the console
         * @param {Mixed} expression
         */
        log: function(expression)
        {
            try
            {
                console.log(expression);
            }
            catch(e){};
        },

        /**
         * Takes a camelCased string and breaks it into it's word components.
         * Taken from http://4umi.com/web/javascript/camelcase.php
         * @param {String}
         * @returns {String}
         */
        camelCaseSplit: function(str)
        {
            var s = this.trim(str);
            return ( /\S[A-Z]/.test( s ) ) ?
                s.replace(/(.)([A-Z])/g, function(t,a,b)
                { return a + ' ' + b.toLowerCase(); }) :
                s.replace(/( )([a-z])/g, function(t,a,b) { return b.toUpperCase(); });
        },

        /**
         * Break a camel cased string apart, separating each word with a space and capitalising each word
         * @param {String} str
         * @returns {String}
         */
        titleCaps: function(str)
        {
            var numbers, parts, result;

            /* There is a bug with the FCL.UTIL.titleCaps which doesn't apply
             * capitalisation if a number is present. Therefore, we convert all
             * numbers to @ symbols, feed the string into the functions, then
             * replace the @ symbols with the original number values before
             * returning result.
             */

            // Replace numbers with @ symbol
            numbers = str.match(/\d+/g);
            str = str.replace(/\d+/g, '@');

            parts = FCL.UTIL.camelCaseSplit(str);
            result = FCL.UTIL._titleCaps(parts);

            // Replace @ symbols numbers
            if(numbers === null) { return result; }

            for(var i=0; i<numbers.length; i++)
            {
                result = result.replace(/@/, numbers[i]);
            }

            return result;
        },

        /**
         * Takes a string as the first argument with n arguments after with which to perform variable substitution
         * @param {String} String for formatting
         * @returns {String}
         * @example FCL.UTIL.format('My name is {1} {2}', 'Ryan', 'Blunden');
         */
        format: function()
        {
            var args = [].slice.call(arguments);
            if(this.toString() != '[object Object]')
            {
                args.unshift(this.toString());
            }

            var pattern = new RegExp('{([1-' + args.length + '])}','g');
            return String(args[0]).replace(pattern, function(match, index) { return args[index]; });
        },

        /**
         * Replace newline characters with <br /> tags
         * @param {String} str
         * @returns {String}
         */
        newLineToBr: function(str)
        {
            return str.replace(/(\r\n|[\r\n])/g, '<br />');
        },

        /**
         * Smart truncate text function which by default, searches for position last instance of stop
         * character within maximum string length at which to truncate string. String will be truncated
         * at max length (len) with finish character (finish) if "ignoreStopChar" is set to true, or if
         * stop character (stopChar is not found within max length (len) of string.
         *
         * Nothing is returned from this function as it replaces existing text with truncated version of
         * each element
         *
         * @param {jQuery} $el jQuery element collection
         * @param {Object} [settings] Truncate text settings
         * @param {Integer} [settings.maxLength=180] Max length of string before truncating
         * @param {String} [settings.finish="&hellip;"] String to append after tuncating
         * @param {String} [settings.stopChar="."] Character to search for and stop at if within string length (len)
         * @param {Boolean} [settings.ignoreStopChar=false] If set to true, string is truncated at len (or less) with finish character regardless of existence of stopchar
         */
        truncateText: function($e, settings)
        {
            if($e.length == 0)
            {
                return $e;
            }

            var defaults =
            {
                maxLength: 180,
                finish: '&hellip;',
                stopChar: '.',
                ignoreStopChar: false
            };

            settings = (typeof settings != 'undefined') ? $.extend(defaults, settings) : defaults;

            return $e.each(function()
            {
                var text = $.trim($(this).text()).replace(/[\n\r\t]+/gm, ' ').replace(/\.[ ]+([A-Z0-9a-z]+)/gm, '. $1').replace(/\.([a-zA-Z0-9]+)/gmi, '. $1')
                var position = settings.maxLength;
                var prevStrPos = 0;
                var search = true;
                var count = 0;

                while(search)
                {
                    // Find position of stop character
                    var strPos = prevStrPos + text.substring(prevStrPos).indexOf(settings.stopChar);

                    // If the prev stop char position and the new position are the same, then we're not finding anything new so break
                    if(strPos >= settings.maxLength || (prevStrPos - 1) == strPos || count > 10)
                    {
                        search = false;
                        break;
                    }

                    // If position of stop char is less than max length (len), then change position
                    if(strPos < settings.maxLength)
                    {
                        position = strPos;
                        prevStrPos = position + 1;
                    }
                }

                var truncatedString = (settings.ignoreStopChar) ? text.substring(0, settings.maxLength).replace('&hellip;', '').replace('…', '') : text.substring(0, position);
                
                // If stop position is found before max length, display truncated string without finish as it's a complete sentence
                // unless ignoreStopChar is set to true
                if(position != settings.maxLength)
                {
                    $(this).html(truncatedString + settings.stopChar);
                    if(settings.ignoreStopChar)
                    {
                        $(this).html($(this).html().substring(0, $(this).html().length-1) + settings.finish);
                    }
                }
                else
                {
                    // if stop char not found and length of text is less than max length, append finish to truncated string
                    if(truncatedString.indexOf(settings.stopChar) == -1 && text.length < settings.maxLength)
                    {
                        $(this).html(truncatedString + settings.finish);
                    }
                    // We're stuck most likely in the middle of a word so back track until we find the first space char, append finish to truncated string
                    else
                    {
                        var spaceCharPos = truncatedString.lastIndexOf(' ');
                        $(this).html(truncatedString.substring(0, spaceCharPos) + settings.finish);
                    }
                }



                var html = $(this).html();
                var lastChar = html.charAt(html.length - 2);
                if(lastChar == settings.stopChar || lastChar == ' ')
                {
                    $(this).html(html.substring(0, html.length-2) + settings.finish);
                }

                return $(this);
            });
        },

        /**
         * Strip out all potentially harmful characters from an input field
         * @param {String} str
         * @returns {String}
         */
        filterInputText: function(str)
        {
            try
            {
                return str.replace(/\s+/gm, ' ').match(/[a-zA-Z0-9\(\)"\', \.!\/:%@&\?\+_=\-\$]+/gm).join('');
            }
            catch(e)
            {
                return '';
            }
        },

        /**
         * Grab the title and break url down into segments to fill out the the default set of Web Trends
         * variables for each page.
         */
        automateWebTrendsVars: function()
        {
            var template = '<meta name="{1}" content="{2}" />';
            var $head = $('head');
            var metaKeys = ['WT.ti', 'WT.cg_n', 'WT.cg_s', 'WT.seg_2'];
            var metaProps  = window.location.pathname.split('/');
            metaProps[0] = document.title;

            for(var i=0; i<metaKeys.length; i++)
            {
                if($head.find('meta[name="'+ metaKeys[i] +'"]').length == 0 && typeof metaProps[i] != 'undefined')
                {
                    $head.append(this.format(template, metaKeys[i], metaProps[i]));
                }
            }
        },

        /**
         * Dynamically register a Google Analytics page view. Default usage is for general enquiries.
         * @param {String} Page view path
         */
        registerGAPageView: function(path)
        {
            try
            {
                window.pageTracker._trackPageview(path);
            }
            catch(e){/* Will Fail if GA script is not on this page */}
        },

        /**
         * Trim function for use if jQuery is not around. Otherwise, just use $.trim
         * @param {String} str
         * @returns {String}
         */
        trim: function(str)
        {
            return str.replace(/^\s+|\s+$/g, '');
        },

        /**
         * Removes non word characters and turns whitespace into hyphens
         * @param  {String} str
         * @returns {String}
         * @deprecated
         */
        sluggify: function(str)
        {
            return this.urlify(str);
        },

        /**
         * Removes non-word characters and turns whitespace into hyphens
         *
         * @param   {String} str
         * @returns {String}
         */
        urlify: function(str)
        {
            return str.replace(/^\s+|\s+$/g, '').replace(/[^a-zA-Z0-9 ]+/g, '').replace(/ /g, '-').toLowerCase();
        },

        /**
         * Return string determine what environment we're running in
         * @returns {String} Returns "development", "staging" or "production"
         */
        getEnv: function()
        {
            var host = window.location.host.substring(0, window.location.host.indexOf('.'));
            var env = '';

            switch(host)
            {
                case 'int':
                    env = 'development';
                    break;

                case 'stage':
                    env = 'staging';
                    break;

                case 'newstage':
                    env = 'staging';
                    break;

                case 'www':
                    env = 'production';
                    break;

                default:
                    env = 'production';
                    break;
            }

            return env;
        },

        /**
         * Load a JavaScript file from any domain with the option of passing in a callback to be executed on load
         * @param {String} path Path to JavaScript file
         * @param [method] Callback to executed on script load
         */
        getJS: function(path, method)
        {
            $(function()
            {
                $.getScript(path, function(data)
                {
                    if(typeof method == 'string')
                    {
                        eval(method);
                    }

                    if(typeof method == 'function')
                    {
                        method();
                    }
                });
            });
        },

        externalLinks: function($el)
        {
            return $el.bind('click', function(e)
            {
                e.preventDefault();
                window.open($(this).attr('href'));
            });
        },

        /**
         * Handle JavaScript errors gracefully so they don't show up in production but provide useful information
         * when in development or staging environments, or if 'debug=True' is found in the query string
         * Extended by Ryan Blunden - 2 July 2010
         * Original by Nicholas C. Zakas - http://www.nczonline.net/blog/2009/04/28/javascript-error-handling-anti-pattern/
         * License: http://www.opensource.org/licenses/mit-license.php
         */
        handleErrors: function(object)
        {
            var func, method;

            for(func in object)
            {
                method = object[func];
                if(typeof method == 'function')
                {
                    object[func] = function(func, method)
                    {
                        return function()
                        {
                            try
                            {
                                return method.apply(this, arguments);
                            }
                            catch(e)
                            {
                                var objectName, errorMessage, lineNumber;
                                objectName = (typeof object.name != 'undefined') ? object.name + '.' : '';
                                lineNumber = (typeof e.lineNumber != 'undefined') ? ' (line number ' + e.lineNumber + ')' : '';
                                errorMessage = 'ERROR: '+ objectName + func + "(): " + e.message + lineNumber;

                                FCL.UTIL.log(errorMessage);
                                if(FCL.UTIL.getUrlVar('debug').toLowerCase() == 'true' || FCL.UTIL.getEnv() == 'development')
                                {
                                    alert(errorMessage);
                                }
                            }
                        };
                    }(func, method);
                }
            }
        },

        /**
         * Alert error message if we're not in production
         * @param {String} message Error message
         */
        throwError: function(message)
        {
            var env = this.getEnv();
            if(env == 'staging' || env == 'development')
            {
                alert('ERROR: '+ message);
            }
        }
    };

    /**
     * Title Caps
     *
     * Ported to JavaScript By John Resig - http://ejohn.org/ - 21 May 2008
     * Original by John Gruber - http://daringfireball.net/ - 10 May 2008
     * License: http://www.opensource.org/licenses/mit-license.php
     */
    (function()
    {
        var small = "(a|an|and|as|at|but|by|en|for|if|in|of|on|or|the|to|v[.]?|via|vs[.]?)";
        var punct = "([!\"#$%&'()*+,./:;<=>?@[\\\\\\]^_`{|}~-]*)";

        /**
         * Apply title caps to the supplied string
         * @param {String} title
         * @returns {String}
         */
        FCL.UTIL._titleCaps = function(title)
        {
            var parts = [], split = /[:.;?!] |(?: |^)["Ò]/g, index = 0;

            while (true)
            {
                var m = split.exec(title);

                parts.push( title.substring(index, m ? m.index : title.length)
                    .replace(/\b([A-Za-z][a-z.'Õ]*)\b/g, function(all)
                    {
                        return /[A-Za-z]\.[A-Za-z]/.test(all) ? all : upper(all);
                    })
                    .replace(RegExp("\\b" + small + "\\b", "ig"), lower)
                    .replace(RegExp("^" + punct + small + "\\b", "ig"), function(all, punct, word)
                    {
                        return punct + upper(word);
                    })
                    .replace(RegExp("\\b" + small + punct + "$", "ig"), upper));

                index = split.lastIndex;

                if(m) parts.push(m[0]);
                else break;
            }

            return parts.join("").replace(/ V(s?)\. /ig, " v$1. ")
                .replace(/(['Õ])S\b/ig, "$1s")
                .replace(/\b(AT&T|Q&A)\b/ig, function(all){
                    return all.toUpperCase();
                });
        };

        function lower(word)
        {
            return word.toLowerCase();
        }

        function upper(word)
        {
          return word.substr(0,1).toUpperCase() + word.substr(1);
        }
    })();

    FCL.UTIL.handleErrors(FCL.UTIL);
    FCL.UTIL.init();
    
})(jQuery);
