"use strict";
/** @namespace The namespace for all FreeLotto JavaScript functions */
var FL = FL || {};

(function ($) {

    // Add :focus selector for jQuery
    // If we ever upgrade to 1.6+, it is built in
    $.expr[':'].focus = function( elem ) {
        return elem === document.activeElement && ( elem.type || elem.href );
    };

    /** @namespace Language-related functions (map, forEach, etc) */
    FL.lang = {};
    
    /* http://dean.edwards.name/base/forEach.js
     forEach, version 1.0
     Copyright 2006, Dean Edwards
     License: http://www.opensource.org/licenses/mit-license.php
     */
    // array-like enumeration
    if (!Array.forEach) { // mozilla already supports this
        Array.forEach = function (array, block, context) 
        {
            for (var i = 0; i < array.length; i++) {
                block.call(context, array[i], i, array);
            }
        };
    }

    // generic enumeration
    Function.prototype.forEach = function (object, block, context) 
    {
        for (var key in object) {
            if (typeof this.prototype[key] === "undefined") {
                block.call(context, object[key], key, object);
            }
        }
    };

    // globally resolve forEach enumeration
    /**
     * Iterate through a list, performing block on each iteration
     * @param {Object, Array} object List over which to iterate 
     * @param {Function} block Function to execute on each iteration 
     * @param {Object} [context] Scope to use when calling block.
     * @default {Object} this
     */
    FL.lang.forEach = function (object, block, context) 
    {
        if (object) {
            var resolve = Object; // default
            if (object instanceof Function) {
                // functions have a "length" property
                resolve = Function;
            }
            else if (object.forEach instanceof Function) {
                // the object implements a custom forEach method so use that
                object.forEach(block, context);
                return;
            }
            else if (typeof object === "string") {
                // the object is a string
                resolve = String;
            }
            else if (typeof object.length === "number") {
                // the object is array-like
                resolve = Array;
            }
            resolve.forEach(object, block, context);
        }
    };
    
    /**
     * Iterate through list, processing with func and, optionally, including scope
     * @requires FL.lang 
     * @param {Object} list
     * @param {Function} func
     * @param {Object} scope 
     * @return {Array}
     */
    FL.lang.map = function (list, func, scope) 
    {
        var arr = [], result;
        FL.lang.forEach(list, function (/* val, ndx, list */) {
            result = func.apply(this, arguments);
            if (result) { arr.push(result); }
        }, scope || this);
        return arr;
    };
    
    /**
     * Takes list of 2 or more objects. Later key/values overwrite earlier key/values.
     * @static
     * @requires FL.lang.forEach
     * @param {Object} 
     * @return {Object}
     */
    FL.lang.merge = function (/* objA, objB, ... objN */) 
    {
        var merged = {};
        FL.lang.forEach(arguments, function (obj)
        {
            merged = (function (first, second)
            {
                FL.lang.forEach(second, function (val, key)
                {
                    first[key] = second[key];                     
                });
                return first;
            }(merged, obj));
        });
        return merged;
    };
    /* eo FL.lang */
    
    /**
     * @class Returns an instance which can be used to for CRUD operations on a cookie.
     * @param {String} sName The name (or key) to use for the cookie
     * @param {String, Object} value What to store in the cookie
     * @param {Object} opts
     * @config {Object, String, Date} expires When does the cookie expire.
     * @config {String} domain (sub)domain to restrict cookie to
     * @config {String} path Path to restrict cookie to. No value defaults to '/';
     * @config {String} secure Is the cookie limited to https?
     * @config {Function} serialize Custom function to use to convert #value into a cookie-ready string
     * @config {Function} deserialize Custom function to use to convert the cookie string into an object
     * @return {FL.Cookie} instance
     * @type {Object}
     * @example new FL.Cookie('settings', {language: 'en'}).setCookie();
     * @example new FL.Cookie('settings', {language: 'en', timezone: 'US-Eastern'}).setCookie();
     * @example new FL.Cookie('played', 'false').setCookie();
     * @example new FL.Cookie('played', 'true', {expires: new Date('01/01/2020')}).setCookie();
     * @example var settings = new FL.Cookie('settings', {language: 'en'});
     * settings.set('timezone', 'US-Eastern');
     * setting.setCookie();
     * @example var settings = new FL.Cookie('settings', {language: 'en'}); 
     */
    FL.Cookie = function (sName, value, opts) 
    {
        var instance  = this,
            oEmpty    = {},
            sv_key    = '__single-value__',
            oValues   = ("string" === typeof value) ? oEmpty[sv_key] = value : value,
            defaults  = {
                expires: undefined,
                domain: undefined,
                path: '/',
                secure: false,
                serialize: serialize,
                deserialize: deserialize 
            },
            config    = FL.lang.merge(defaults, (opts || {})),
            curr_vals = getValuesFromCookieString() || {};

        oValues = FL.lang.merge(curr_vals, oValues);

        /**#@+ @public */
        /** @property {String, Number, Date} expires */
        this.expires = config.expires;

        /** @property {String} domain */
        this.domain = config.domain;

        /** @property {String} path */
        this.path = config.path;

        /** @property {Boolean} secure */
        this.secure = config.secure;

        
        /**
         * Sets key to value internally
         * @param {Object} key
         * @param {Object} value
         * @return {FL.Cookie} instance
         * @type {Object} 
         */
        this.set = function (key, value) 
        {
            if (sv_key in oValues) {
                oValues[sv_key] = key;
            }
            else {
                oValues[key] = value;
            }

            return instance; // allows chaining
        };

        /**
         * Returns the value stored for key in the internal structure {oValues}
         * @param {String} key
         * @return {String} value
         */
        this.get = function (key) 
        {
            if (sv_key in oValues) {
                return oValues[sv_key];
            }
            else if (key) {
                return oValues[key];
            }
        };
        
        /**
         * Deletes the given key from the cookie
         * @param {String} key
         * @return {FL.Cookie} instance
         * @type {Object}
         */
        this.del = function (key)
        {
            delete oValues[key];
            
            return instance;
        };

        /**
         * Serializes values object and sets the cookie in the browser
         * @return {FL.Cookie} instance
         * @type {Object}
         */
        this.setCookie = function () 
        {
            var parts   = [ [sName, getValueString()] ],
                expires = getDateString();

            if (expires)       { parts.push(['expires', expires]); }
            if (config.domain) { parts.push(['domain', config.domain]); }
            if (instance.path) { parts.push(['path', instance.path]); }

            var cookie_string = FL.lang.map(parts, function (arr) {
                    return arr.join('=');
                }).join('; ');

            if (config.secure) { cookie_string += '; secure'; }

            document.cookie = cookie_string;

            return instance; // allows chaining
        };

        /**
         * Return the internal object which stores the values to be serialized
         * @return {Object} oValues
         */
        this.getValues = function ()
        {
            return (sv_key in oValues) ? oValues[sv_key] : oValues;
        };
        /**#@- */

        /**#@+ @private */
        /**
         * @return {Object} of key/value pairs for *currently set* cookie
         */
        function getValuesFromCookieString() 
        {
            var aCookies = document.cookie.split('; '), 
                oCookie;

            for (var i = 0, l = aCookies.length; i < l; i++) {

                var sCookie   = aCookies[i], 
                    matches   = sCookie.match(/^(.+?)=(.+)$/);

                if(matches) {
                    var cookieID  = matches[1], 
                        cookieVal = matches[2];

                    if (cookieID === sName) {
                        oCookie = config.deserialize(cookieVal);
                        break;
                    }
                }
            }
            
            return oCookie;
        }
        
        /**
         * @return {String} serialized string of cookie values
         */
        function getValueString() 
        {
            if (sv_key in oValues) {
                return oValues[sv_key];
            } 
            else {
                return config.serialize(oValues);
            }
        }
        
        /**
         * @return {String} UTC Date string appropriate for cookie
         */
        function getDateString() 
        {
            if ((/GMT|UTC/i).test(instance.expires) || 
                (/\//).test(instance.expires) 
            ){
                return new Date(instance.expires).toUTCString();
            }
            else if ("number" === typeof instance.expires) {
                return new Date(instance.expires).toUTCString();
            }
            else if (instance.expires instanceof Date) {
                return instance.expires.toUTCString();
            }

            return;
        }
        
        /**
         * @param {Object} obj
         * @requires FL.lang.map
         */
        function serialize(obj) 
        {
            var pairs = FL.lang.map(obj, function (val, key)
                {
                    if (obj[key]) {
                        return encodeURIComponent(key) +'='+ encodeURIComponent(val);
                    }
                    else {
                        return false;
                    }
                });

            return pairs.join('&');
        }

        /**
         * @param {String} str
         * @requires FL.lang.forEach
         */
        function deserialize(str) 
        {
            var pairs = str.split('&'),
                obj   = {};

            if (! (/\=/).test(pairs)) {
                obj[sv_key] = pairs[0];
            }
            else {
                FL.lang.forEach(pairs, function (pair) 
                {
                    var parts = pair.split('='),
                        key   = decodeURIComponent(parts[0]),
                        val   = decodeURIComponent(parts[1]);

                    obj[key] = val;
                });
            }

            return obj;
        }
        /**#@- */    

        return this;
    }; // eo FL.Cookie constructor

    /**
     * Turn a string of key=value pairs into an object like {key: 'value'}
     * @param {String} str key=value pairs
     * @return {Object}
     */
    FL.deserialize = function (str)
    {
        var ndx   = str.indexOf('?') + 1,
            pairs = str.slice(ndx).split('&'),
            obj   = {},
            key, val, kv;

        FL.lang.forEach(pairs, function (pair) 
        {
            kv = pair.split('=');

            if (kv.length !== 1) {

                key = unescape(kv[0]);
                val = unescape(kv[1]);
                if ( (/^[0-9]+$/).test(val) ){ val = parseInt(val); }

                if ( (/[%5B%5D|\[\]]/).test(key) // array-like key
                    || (key in obj)              // already seen/stored key
                ){
                    if (obj[key]) {
                        if ( !(obj[key] instanceof Array) ) {
                            obj[key] = [obj[key]];
                        }
                        obj[key].push(val);
                    }
                    else {
                        obj[key] = [val];
                    }
                }
                else {
                    obj[key] = val;
                }
            }
        });

        return obj;
    };

    /**
     * Access the values in the query string. 
     * When called without arguments, return the entire object
     * @param {Object} [opts]
     * @config {String, Array} key The key (or array of keys) whose value should be returned
     * @config {String} url The URL to use when extracting key/values
     * @returns {String, Object, Array}
     * @example var obj = FL.queryParams(); // returns object based on query string params
     * @example var str = FL.queryParams({key: 'user'}); // returns query string value of 'user'
     * @example var obj = FL.queryParams({url: 'foo=bar&baz=bam'}); // returns {foo: 'bar', baz: 'bam'}
     * @example var str = FL.queryParams({key: 'baz', url: 'foo=bar&baz=bam'}); // returns 'bam'
     * @example var obj = FL.queryParams({key: ['user', 'baz'], url: 'user=name&foo=bar&baz=bam'}); // returns {user: 'name', baz: 'bam'}
     */
    FL.queryParams = function(opts) {
        opts = opts || {};
        var url    = opts.url || window.location.search,
            cached = ('qso' in window.location) || false,
            obj    = cached ? window.location.qso : FL.deserialize(url),
            out;

        if (!cached) {
            try { window.location.qso = obj; } catch (e) {}
        }

        if (opts.key) {
            if (opts.key instanceof Array) {
                out = {};
                FL.lang.forEach(opts.key, function (k) {
                    out[k] = obj[k];
                });
            }
            else {
                out = obj[opts.key];
            }
        }
        else {
            out = obj;
        }

        FL.lang.forEach(out, function(v, k) {
            if(v) {
                out[k] = v.toString().replace(/\+/g, " ");
            }
        });

        return out;
    };

    /**
     * Turn an object like {key: 'value', another: 'pair'} into 'key=value&another=pair'
     * @param {Object} obj An object like {key: 'value', another: 'pair'}
     * @return {String}
     */
    FL.serialize = function (obj)
    {
        var parts = [],
            push  = function (key, val) {
                parts.push(encodeURIComponent(key) + '=' + encodeURIComponent(val));
            };

        for (var key in obj) {
            if (obj.hasOwnProperty(key)) {
                var val = obj[key];
                if (val instanceof Array) {
                    for (var i=0, l=val.length; i<l; i++) { push(key, val[i]); }
                } else {
                    push(key, val);
                }
            }
        }
        
        return parts.join('&');    
    };

    /**
     * Given an object, return a string of key/value pairs. Automatically determines whether string requires a '?' 
     * @static
     * @param {Object} obj The object to serialize
     * @param {String} [url] The root URL to use (creating a fully-qualified URL)
     * @return {String}
     * @example FL.obj2URL({key: 'val', foo: 'bar'}); // '?key=val&foo=bar'
     * @example FL.obj2URL({'arr[]': [1,2,3]}); // '?arr[]=1&arr[]=2&arr[]=3'
     * @example FL.obj2URL({user: 'name'}, 'http://domain.com/'); // 'http://domain.com/?user=name'
     * @example FL.obj2URL({user: 'name'}, 'http://domain.com/?login'); // 'http://domain.com/?login&user=name'
     */
    FL.obj2URL = function (obj, url)
    {
        url = url || '';
        var root = '';

        if (url) {
            var ndx_orig = url.indexOf('?'),
                ndx = (ndx_orig === -1) ? url.length : ndx_orig;

            root = url.slice(0, ndx);
            obj  = FL.lang.merge(FL.queryParams({url: url}), obj);
        }

        return root +'?'+ FL.serialize(obj);
    };

    /**
     * Change the language value in the settings cookie
     * @static 
     * @requires FL.Cookie
     * @param {String} lang The two-character country code to set in the cookie
     * @return {FL.Cookie} instance
     * @type {Object}
     */
    FL.setLanguageCookie = function (lang)
    {
        if (!lang) { 
            return false; // language is required 
        } else {
            return new FL.Cookie('settings', {language: lang}).setCookie();
        }        
    };
    
    /**
     * Change the language in the 'settings' cookie and, optionally, perform some actions on a form.
     * @static 
     * @requires jQuery
     * @requires FL.Cookie 
     * @param {String} lang The two-character country code to set in the cookie      
     * @param {Object} [opts] 
     * @config {String} disable CSS selector string to use when determining which fields to disable 
     * @config {String, DOMElement} form The form to submit
     * @config {String} method Form method The method ('get' or 'post') to use for the form.
     * @example FL.changeLanguage('en'); // just set the cookie and return
     * @example FL.changeLanguage('es', {disable: '#id1, #id2'}); // set and disable '#id1, #id2'
     * @example FL.changeLanguage('ie', {form: '#formid'});
     * @example FL.changeLanguage('ie', {form: DOM_element});
     * @example FL.changeLanguage('ie', {form: '#formid', method: 'get'});
     */
    FL.changeLanguage = function (lang, opts) 
    {
        opts = opts || {};

        //1. Update the cookie with the new language
        if (!FL.setLanguageCookie(lang)) { return false; }

        //2. Turn off any fields supplied
        // set disabled CSS selector if it isn't already set
        opts.disable = opts.disable || ".form_submit_flag, input[name=lang]";
        var $fields = jQuery(opts.disable);
        $fields.attr('disabled', true);

        //3. get the session cookie username and FREELOTTOID cookie
        var name = new FL.Cookie('session').get('username'),
            flid = new FL.Cookie('FREELOTTOID').get();

        // need a DOM reference to a form element
        // allow for several ways to specify the form
        var form;

        if (opts.form) {
            form = ("string" === typeof opts.form) ? 
                // first try to get by id, then by selector
                document.getElementById(opts.form) || jQuery(opts.form).get(0) :
                // (assuming) it's a DOM element
                opts.form;
        } 
        else if (opts.disable && $fields.get(0)) {  // last chance
            form = $fields.get(0).form;             // get it from the disabled fields
        }

        var obj_form_values  = form ? FL.deserialize( jQuery(form).serialize() ) : {},
            obj_query_string = FL.queryParams(),
            obj_merged       = FL.lang.merge(obj_query_string, obj_form_values);

        // 4. add form values/query params depending on method for username/cookieusername/language
        if (name && flid) {
            obj_merged.chglang_username = name;
            obj_merged.chglang_flid = flid;
        }

        // Remove lang from resulting query string
        delete obj_merged.lang;

        if (form) {
            // update the method if we're given one
            if (opts.method) { form.method = opts.method; }
            form.action = FL.obj2URL(obj_merged);
            form.submit();
        }
        else {
            document.location.replace(FL.obj2URL(obj_merged, document.location.href));            
        }

    };
    
    /**
     * Page-specific functions. 
     * @namespace
     * @example FL.pages.play = (function () { ... })(); // return functions for play.asp
     * @example FL.pages.tww = (function () { ... }(); // return functions for ThisWeeksWinners.asp 
     */
    FL.pages = {};

    /**
     * Short-lived page-specific variables. Useful when having a page set a value (usually a JSON-ified Perl object/value) to be used by FL.pages functions.
     * @namespace
     * @example FL.page.countries = &lt;%= $json->encode(\%countries) %gt;; // which can be accessed by another function if passing the variable is not practical/possible
     */
    FL.page = {};

    /** 
     * @namespace FreeLotto functions added to the jQuery namespace
     */
    $.fn.extend(
    {
        /**#@+
         * @memberOf jQuery
         * @function
         */
        /**
         * Generate (and assign) an ID for a DOM element
         * @name generateID
         * @param {Object} options
         * @config {String} prefix Prefix for the id, an integer will be appended. @default 'fl_gen_'
         * @config {Boolean} force Overwrite with new ID even if the DOM element already has one? @default false
         * @example jQuery('.menu_left').generateID({prefix: 'your_prefix_here_'}); // [span#your_prefix_here_6.menu_left, span#your_prefix_here_7.menu_left, span#your_prefix_here_8.menu_left]
         * @example jQuery('#logo').generateID({prefix: 'your_prefix_here_'}); // [img#logo.howtoplaypopup LogoTopLef...er_new.gif]
         * @example jQuery('#logo').generateID({prefix: 'your_prefix_here_', force: true}); // [img#your_prefix_here_2.howtoplaypopup LogoTopLef...er_new.gif]
         */
        generateID: function (options)
        {
            options = options || {};

            var defaults = {
                    prefix: 'fl_gen_',
                    force: false
                },
                settings = $.extend(defaults, options);

            return this.each(function ()
            {
                if (settings.force || (!this.id) ) {
                    this.id = settings.prefix + $.data(this);
                }
            });
        },

        /**
         * Simple show/hide of menus
         * @name simpleMenu
         * @param {Object} menu The CSS selector for the item to be shown
         * @param {Object} options
         * @config {String} speed
         * @example $("#show_language").simpleMenu("#show_language_links");
         * @example $("#logo").simpleMenu("#howtoplaypopup", {speed: "slow"});
         */
        simpleMenu: function (menu, options) 
        {
            if (! menu) { throw "You must include a selector for menu"; }
            options = options || {};

            var $menu = $(menu),
                defaults = {},
                settings = $.extend(defaults, options);

            return this.each(function () 
            {
                var $trigger = $(this);
                $trigger.click(function (e) { $menu.toggle(settings.speed); });
            });
        },

        /**
         * Force a handler to be triggered *before* any others.
         * @param {String} types One or more event types separated by a space
         * @param {Object} data Additional data passed to the event handler as event.data
         * @param {Function} fn A function to bind to the event on each of the set of matched elements, passed an event object. 
         */
        bindFirst: function (types, data, fn)
        {            
            return this.each(function (index, elem)
            {
                $.each(types.split(/\s+/), function (index, type)
                {
                    var eventType = type.split(".").shift(), 
                        events = $.data(elem, "events"), 
                        originalEvents = events && events[eventType] || null;

                    if (!originalEvents) { return $(elem).bind(type, data, fn); }

                    $(elem).unbind(type).bind(type, data, fn);
                    $.extend(events[eventType], originalEvents);
                });
            });
        }
        /**#@-*/
    });

}(jQuery));

// This will run on every page
$("document").ready(function ()
{
    // set up the language dropdown.
    $("a.language_links").click(function (e) {
            e.preventDefault();
            var lang = this.href.replace(/^.*\#/, "");
            FL.changeLanguage(lang, FL.page.changelanguage_options || {});
    });

    // set up logo how to play  
    $("#logo").simpleMenu("#howtoplaypopup", {speed: "slow"});
    $("#btn-howtoplay-close").simpleMenu("#howtoplaypopup", {speed: "slow"});
    
    // set up select language mouseover
    $("#show_language").simpleMenu("#show_language_links");
    $("#btn-language-select-close").simpleMenu("#show_language_links");

});
