(function(window) {

var Prepop = {
    _data: {},
    _emptycb: function(ret_data) { return ret_data || {}; },
    _translations: {},

   /**
    * Translates data according to provided source-destination mapping.
    * @param object src_dest_map Source-to-Destination key translation mapping
    * @param object data Data to use against the map
    * @return object
    * @access private
    */
    _translateData: function(src_dest_map, data) {
        var dest = {};
        if (!src_dest_map) src_dest_map = {};
        if (!data) data = {};

        for (var from_key in src_dest_map) {
            if (data[from_key]) {
                var dest_key = src_dest_map[from_key];

                switch (typeof dest_key) {
                    case 'string': // direct translation
                        dest[dest_key] = data[from_key];
                        break;
                    case 'function': // callback function will provide translation
                        var func = dest_key,
                            func_data = func(data[from_key]);

                        if (func_data) {
                            for (var dest_key in func_data) {
                                dest[dest_key] = func_data[dest_key];
                            }
                        }
                        break;
                }
            }
        }
        return dest;
    },

    /**
     * Sets a value for an internal data key.
     * @param mixed key String key to use, or dictionary or key/value pairs to overwrite internal data.
     * @param mixed Value to set [optional, null default].
     */
    set: function(key, value) {
        var datatype = (typeof key);

        if (datatype == 'object') { // dict
            Prepop._data = key;
        } else if (datatype == 'string') { // key name provided
            Prepop._data[key] = (typeof value == 'undefined' ? null : value);
        }
    },

    /**
     * Retrieves a value from an internal data key.
     * @param mixed key String key to use [optional, object with all key/value pairs returned if not provided]
     * @return mixed
     */
    get: function(key) {
        var ret = Prepop._data || {};
        if (key) {
            var val_type = Object.prototype.toString.call(ret[key]).toLowerCase();
            if (val_type != '[object window]') {
                val_type = typeof ret[key];
            }
            return val_type == 'null' || val_type == 'undefined' ? '' : ret[key];
        }
        return ret;
    },

    /**
     * Defines a translation that can be used with 'getTranslated()' method.
     * @param string id Identifier for translation
     * @param object src_dest_map Source-to-Destination key translation mapping
     *      NOTE:
     *          The destination keys can also be a callback function which is passed the source value.
     *          The return value of the callback should be an object whose keys and values will be added into the final translated object.
     *
     *          Additionally, any keys set that are not provided for in src_dest_map will
     *          be excluded from the returned object from `getTranslated()`.
     *
     *      Example:
     *      {
     *          'firstName': '_first_name',
     *          'lastName': '_last_name',
     *          'dob': function (dob) {
     *              var m = (dob+'').match(/\d{4}-\d{2}-\d{2}/);
     *              return m ? {
     *                  'month': m[2],
     *                  'day'  : m[3],
     *                  'year' : m[1],
     *              } : false;
     *          }
     *      }
     *
     * @param function after_trans_callback Callback to execute after translating that can be used for more custom translations [optional].
     *      NOTE: The callback is passed the final translated object.
     *
     *      Example:
     *      function (ret_data) {
     *          ret_data['always_set'] = 'me';
     *          if ('my_target_dest_key' in ret_data) {
     *              ret_data['renamed_key'] = ret_data['my_target_dest_key'];
     *              del ret_data['my_target_dest_key'];
     *          }
     *          return ret_data;
     *      }
     */
    setTranslation: function(id, src_dest_map, after_trans_callback) {
        Prepop._translations[id] = [
            src_dest_map || {}, (typeof after_trans_callback == 'function') ? after_trans_callback : Prepop._emptycb
        ];
    },

   /**
    * Unsets a previously defined translation.
    * @param string id Identifier for an existing translation
    */
    unsetTranslation: function(id) {
        if (id && (id in Prepop._translations)) {
            delete Prepop._translations[id];
        }
    },
    
   /**
    * Translates internal data (or provided data) according to defined translation.
    * @param string id Identifier for an existing translation
    * @param data Data to use [optional, uses internal `get()` data]
    * @return map of values or false
    */
    getTranslated: function(id, data) {
        if (typeof id == 'undefined') return false;
        if (!data) data = Prepop.get();

        var translation = Prepop._translations[id];
        if (translation) {
            var src_dest_map = translation[0],
                callback = translation[1];

            // let callback modify returned data
            return callback(Prepop._translateData(src_dest_map, data));
        }
        return false;
    },
                      
   /**
    * Returns a state code for the specified abbreviation.
    */
    getStateCode: function(abbrev) {
        var state_map = {
            'AA': '61', 'AB': '74', 'AE': '60', 'AE': '64', 'AE': '63', 'AE': '62', 'AK':  '2', 'AL':  '1', 'AP': '65', 'AR':  '5', 'AS':  '3',
            'AZ':  '4', 'BC': '71', 'CA':  '6', 'CO':  '7', 'CT':  '8', 'DC': '56', 'DE':  '9', 'FL': '11', 'FM': '10', 'GA': '12', 'GU': '13',
            'HI': '14', 'IA': '18', 'ID': '15', 'IL': '16', 'IN': '17', 'KS': '19', 'KY': '20', 'LA': '21', 'MA': '25', 'MB': '70', 'MD': '24',
            'ME': '22', 'MH': '23', 'MI': '26', 'MN': '27', 'MO': '29', 'MP': '39', 'MS': '28', 'MT': '30', 'NB': '69', 'NC': '37', 'ND': '38',
            'NE': '31', 'NH': '33', 'NJ': '34', 'NL': '75', 'NM': '35', 'NS': '68', 'NU': '77', 'NV': '32', 'NY': '36', 'OH': '40', 'OK': '41',
            'ON': '66', 'OR': '42', 'PA': '44', 'PE': '72', 'PR': '45', 'PW': '43', 'QC': '67', 'RI': '46', 'SC': '47', 'SD': '48', 'SK': '73',
            'TN': '49', 'TX': '50', 'UT': '51', 'VA': '54', 'VI': '53', 'VT': '52', 'WA': '55', 'WI': '58', 'WV': '57', 'WY': '59', 'YT': '76'
        };
        return state_map[abbrev] || false;
    },

   /**
    * Returns a state abbreviation for the specified code.
    */
    getStateAbbrev: function(code) {
        var state_map = {
            '61': 'AA', '74': 'AB', '60': 'AE', '64': 'AE', '63': 'AE', '62': 'AE',  '2': 'AK',  '1': 'AL', '65': 'AP',  '5': 'AR',  '3': 'AS',
             '4': 'AZ', '71': 'BC',  '6': 'CA',  '7': 'CO',  '8': 'CT', '56': 'DC',  '9': 'DE', '11': 'FL', '10': 'FM', '12': 'GA', '13': 'GU',
            '14': 'HI', '18': 'IA', '15': 'ID', '16': 'IL', '17': 'IN', '19': 'KS', '20': 'KY', '21': 'LA', '25': 'MA', '70': 'MB', '24': 'MD',
            '22': 'ME', '23': 'MH', '26': 'MI', '27': 'MN', '29': 'MO', '39': 'MP', '28': 'MS', '30': 'MT', '69': 'NB', '37': 'NC', '38': 'ND',
            '31': 'NE', '33': 'NH', '34': 'NJ', '75': 'NL', '35': 'NM', '68': 'NS', '77': 'NU', '32': 'NV', '36': 'NY', '40': 'OH', '41': 'OK',
            '66': 'ON', '42': 'OR', '44': 'PA', '72': 'PE', '45': 'PR', '43': 'PW', '67': 'QC', '46': 'RI', '47': 'SC', '48': 'SD', '73': 'SK',
            '49': 'TN', '50': 'TX', '51': 'UT', '54': 'VA', '53': 'VI', '52': 'VT', '55': 'WA', '58': 'WI', '57': 'WV', '59': 'WY', '76': 'YT'
        };
        return state_map[code] || false;
    },

   /**
    * Converts a state value into either it associated code, abbreviation, or a list of both values.
    * @param string value State value to convert.
    * @param string conv_type Type of conversion to perform ('code', 'abbrev', or 'both') and return
    * @return string|list|bool (false if invalid conv_type)
    */
    getStateVal: function(value, conv_type) {
        if (!value) return '';
        value = (value+'').toUpperCase();

        var code, abbrev;
        if (SC_REG.test(value)) {
            code = value;

            if (conv_type != 'code') {
                abbrev = Prepop.getStateAbbrev(code);
            }
        } else {
            abbrev = value;

            if (conv_type != 'abbrev') {
                code = Prepop.getStateCode(abbrev);
            }
        }

        if (conv_type == 'abbrev') {
            return abbrev;
        }
        if (conv_type == 'code') {
            return code;
        }
        if (conv_type == 'both') {
            return [abbrev, code];
        }
        return false;
    },
                  
   /**
    * Returns a list of state codes and their respective names.
    */
    getStateList: function() {
        return {
            '1': 'Alabama', '2': 'Alaska', '4': 'Arizona', '5': 'Arkansas',
            '61': 'Armed Forces Americas','63': 'Armed Forces Europe', '65': 'Armed Forces Pacific',
            '6': 'California', '7': 'Colorado', '8': 'Connecticut', '9': 'Delaware',
            '56': 'District of Columbia', '11': 'Florida', '12': 'Georgia', '14': 'Hawaii',
            '15': 'Idaho', '16': 'Illinois', '17': 'Indiana', '18': 'Iowa',
            '19': 'Kansas', '20': 'Kentucky', '21': 'Louisiana', '22': 'Maine',
            '24': 'Maryland', '25': 'Massachusetts', '26': 'Michigan', '27': 'Minnesota',
            '28': 'Mississippi', '29': 'Missouri', '30': 'Montana', '31': 'Nebraska',
            '32': 'Nevada', '33': 'New Hampshire', '34': 'New Jersey', '35': 'New Mexico',
            '36': 'New York', '37': 'North Carolina', '38': 'North Dakota', '40': 'Ohio',
            '41': 'Oklahoma', '42': 'Oregon', '44': 'Pennsylvania', '46': 'Rhode Island',
            '47': 'South Carolina', '48': 'South Dakota', '49': 'Tennessee', '50': 'Texas',
            '51': 'Utah', '52': 'Vermont', '54': 'Virginia', '55': 'Washington',
            '57': 'West Virginia', '58': 'Wisconsin', '59': 'Wyoming'
        };
    },

    /**
     * Fetches location data for a given postal code.
     * @param string|int postal Postal code
     * @param function callback Callback function to execute when ajax request is successful
     */
    fetchLocationData: function(postal, callback) {
        if (typeof callback != 'function' || !postal) return false;

        $.ajax({
            url: '/api/get_loc/' + postal + '/',
            cache: true,
            dataType: 'json',
            success: function(data, status) {
                if (data.results) {
                    callback(data.results);
                }
            }
        });
    },

    /**
     * Prepop the given jQuery elements with a given value.
     * @param jQuery elements List of jQuery elements.
     * @param mixed value The value(s) to prepop the element with.
     */
    setElements: function(elements, value) {
        if (elements.is('select:not([multiple]), input[type=text], textarea')) {
            elements.filter('select:not([multiple]), input[type=text], textarea').val(value).trigger('change');
        }
        if (elements.is('input[type=radio],input[type=checkbox],select[multiple]')) {
            if (!$.isArray(value)) {
                value = (value+'').split(',');
            }
            elements.filter('input[type=radio],input[type=checkbox],select[multiple]').val(value).trigger('change');
        }
    },

    /**
     * Determines if the provided value matches a list of strings representing various types of boolean values.
     * @param string value (e.g. Yes, no, Y, N, etc)
     * @return Array of strings or false if no matches found
     */
    getBoolValues: function(value) {
        value = (value+'').toLowerCase();
        var yes_values = ['T', 't', '1', 'true', 'True', 'y', 'Y', 'yes', 'Yes'];
        var no_values = ['F', 'f', '0', 'false', 'False', 'n', 'N', 'no', 'No'];

        if (yes_values.indexOf(value) >= 0) {
            return yes_values;
        }
        if (no_values.indexOf(value) >= 0) {
            return no_values;
        }
        return false;
    }
};

// setup our translations

var SC_REG = /^\d{1,2}$/;

var yd_trans = {
    'firstName'      : 'firstName',
    'lastName'       : 'lastName',
    'email'          : 'email',
    'phone'          : 'phone',
    'address1'       : 'address1',
    'address2'       : 'address2',
    'city'           : 'city',
    'state'          : 'state',
    'postal'         : 'postal',
    'program_id'     : 'program_id',     // the program category
    'school_prog_id' : 'school_prog_id', // the specific school program

    // classnames
    'phonetype'      : 'phonetype',
    'phone2'         : 'phone2',
    'phone2type'     : 'phone2type',
    'gradyear'       : 'gradyear',
    'edulevel'       : 'edulevel',
    'military'       : 'military'
};

Prepop.setTranslation(0, yd_trans, function(ret_data) {
    if ('state' in ret_data) {
        ret_data['state'] = Prepop.getStateVal(ret_data['state'], 'abbrev');
    }

    if ('military' in ret_data) {
        var vals = Prepop.getBoolValues(ret_data['military']);
        if (vals && vals.length > 0) {
            ret_data['military'] = vals[0];
        }
    }
    return ret_data;
});

Prepop.setTranslation(1,
    {
        'firstName'  : 'firstname',
        'lastName'   : 'lastname',
        'email'      : 'email',
        'phone'      : 'phone',
        'address1'   : 'address1',
        'address2'   : 'address2',
        'city'       : 'city',
        'state'      : 'state',
        'postal'     : 'zip'
    },

    // always append country to results
    function(ret_data) {
        ret_data['country'] = 'USA';
        if ('state' in ret_data) {
            ret_data['state'] = Prepop.getStateVal(ret_data['state'], 'abbrev');
        }
        return ret_data;
    }
);

Prepop.setTranslation(2,
    {
        'firstName'   : 'df_fname',
        'lastName'    : 'df_lname',
        'email'       : 'df_email',
        'phone'       : function(value) {
            value = (value+'').replace(/[^\d+]/g, '');

            return {
                'df_day_phone_area'   : value.substr(0, 3),
                'df_day_phone_prefix' : value.substr(3, 3),
                'df_day_phone_suffix' : value.substr(6, 4)
            };
        },
        'address1'    : 'df_address',
        
        // map to placeholder value, so callback can handle appropriately
        'address2'    : '__ADDRESS2__',

        'city'        : 'df_city',
        'state'       : 'df_state',
        'postal'      : 'df_zip'
    },
    
    // merge address values into a single key
    function(ret_data) {
        var addr = '', val = '';

        if ('df_address' in ret_data) {
            val = ret_data['df_address'];
            if (val) addr = val;
        }

        if ('__ADDRESS2__' in ret_data) {
            var val = ret_data['__ADDRESS2__'];
            delete ret_data['__ADDRESS2__'];
            if (val) addr += ', ' + val;
        }
        if (addr) ret_data['df_address'] = addr;

        if ('df_state' in ret_data) {
            ret_data['df_state'] = Prepop.getStateVal(ret_data['df_state'], 'abbrev');
        }
        return ret_data;
    }
);

// for form page, custom translator
Prepop.setTranslation('form', yd_trans, function(ret_data) {
    if ('state' in ret_data) {
        ret_data['state'] = Prepop.getStateVal(ret_data['state'], 'both');
    }

    if ('military' in ret_data) {
        var vals = Prepop.getBoolValues(ret_data['military']);
        if (vals && vals.length > 0) {
            ret_data['military'] = vals || null;
        }
    }

    return ret_data;
});

window.Prepop = Prepop; // expose to global object

})(window);

