/**
 * This is a jquery plugin used for initializing/managing text fields that geocode (search, add ride, etc.)
 *
 * The locationinput plugin is basically a complicated wrapper for the autocomplete plugin and google 
 * geocoding. It currently is working relatively well cross browser, though there are still several
 * main issues. See ticket #365 for one of the issues. There is also some hacky code (related to that
 * ticket) being implemented for timing issues in the add_ride.js file.
 *
 * Dependencies:
 * - jQuery
 * - jquery.autocomplete.js plug in
 * - breakout_address.js
 *
 * @author Dylan
 *
 * @copyright © 2012 Zimride
 */

// couched in anonymous function to avoid conflicts
// preceded by a semi-colon in case statement before calling this plugin wasn't ended with a semicolon
;(function($) {
        // attach our method to jQuery.fn
        $.fn.locationinput = function(prefix, locations, options) {
            // use default options except where client given options override them
            options = $.extend(true, {}, $.locationinput.defaults, options);
            
            // call our function
            return this.each(function() {
                new $.locationinput(this, prefix, locations, options);
            });
        };
        
        // attach our function (the workhorse) to jQuery
        // params:
        //  - element: is a reference to the matched objects
        //  - prefix: is the string prefix (name of the location field) that is used to prefix all of the hidden fields. Keep
        //    in mind that to avoid conflicts, no two fields that have locationinput called on them on the same page should
        //    use the same prefix
        //  - locations: locations to populate the autocomplete with
        //  - options: options that dictate behavior. defaulted from the $.locationinput.defaults object and overwritten by
        //    user supplied options
        $.locationinput = function(element, prefix, locations, options) {
            // $input is the jQuery object representing the field that locationinput is attached to
            var $input = $(element);
            // parent_form is the form surrounding the field
            var parent_form = $input.closest('form');
            // fieldValue is the current text in the field
            var fieldValue = $input.val();
            
            // First add necessary hidden input fields to parent form
            form_html = '<input type="hidden" name="' + prefix + '_name" value="' + options.loc_values.name + '" />';
            form_html += '<input type="hidden" name="' + prefix + '_full_text" value="' + options.loc_values.full_text + '" />';
            form_html += '<input type="hidden" name="' + prefix + '_error_code" value="' + options.loc_values.error_code + '" />';
            form_html += '<input type="hidden" name="' + prefix + '_address" value="' + options.loc_values.address + '" />';
            form_html += '<input type="hidden" name="' + prefix + '_city" value="' + options.loc_values.city + '" />';
            form_html += '<input type="hidden" name="' + prefix + '_state" value="' + options.loc_values.state + '" />';
            form_html += '<input type="hidden" name="' + prefix + '_zip" value="' + options.loc_values.zip + '" />';
            form_html += '<input type="hidden" name="' + prefix + '_country" value="' + options.loc_values.country + '" />';
            form_html += '<input type="hidden" name="' + prefix + '_lat" value="' + options.loc_values.lat + '" />';
            form_html += '<input type="hidden" name="' + prefix + '_lng" value="' + options.loc_values.lng + '" />';
            form_html += '<input type="hidden" name="' + prefix + '_location_key" value="' + options.loc_values.location_key + '" />';
            form_html += '<input type="hidden" name="' + prefix + '_user_lat" value="' + options.loc_values.user_lat + '" />';
            form_html += '<input type="hidden" name="' + prefix + '_user_lng" value="' + options.loc_values.user_lng + '" />';
            form_html += '<input type="hidden" name="' + prefix + '_user_country" value="' + options.loc_values.user_country + '" />';
            $(parent_form).append(form_html);
            
            // And make sure that input field is named correctly:
            $($input).attr('name', prefix);
            
            // instantiate geocoder
            var geocoder = new google.maps.Geocoder();
            
            // initialize bounds (based on the user's ip)
            // this algorithm works only on the northern-west hemisphere
            var bounds = '';
            var user_country = '';
            if (options.loc_values.user_lat != '') {
                var miles = 200;
                var delta_lat = miles / 69; // bound height
                var delta_lng = (miles / Math.abs(Math.cos(+options.loc_values.user_lat*Math.PI/180) * 69)); // bound width
                
                northernLat = (+options.loc_values.user_lat)+delta_lat;
                westernLng = (+options.loc_values.user_lng)-delta_lng;
                southernLat = (+options.loc_values.user_lat)-delta_lat;
                easternLng = (+options.loc_values.user_lng)+delta_lng;            
                bounds = new google.maps.LatLngBounds(
                    new google.maps.LatLng(southernLat,westernLng), 
                    new google.maps.LatLng(northernLat, easternLng)
                );
                user_country = options.loc_values.user_country;
            }
            
            // Add change event to input field
            // on change, if there is anything in the field, geocode it
            // else, clear all the hidden fields and handle onEmpty events
            $($input).change(function(){
                  	if($($input).hasClass('clearingText')) {
                  		return;
                  	}

                    if ($($input).val()) {
                        if (typeof(bounds) == 'string') {
                            geocoder.geocode({'address':$($input).val()}, geocodeCallback);
                        } else {
                            geocoder.geocode({'address':$($input).val(), 'bounds': bounds, 'region': user_country}, geocodeCallback);
                        }
                    } else {
                        clearHiddenFields();

                        // onEmpty callback
                        options.onEmpty($input);
                        $($input).removeClass(options.invalid_class);
                        $($input).removeClass(options.valid_class);
                    }
            });
            
            // Attach autocomplete to field if locations are provided and the options indicate that autocomplete should be on
            if (locations !== null && options.autocompleteOn) {
                // attach the autocomplete function to the input field
                // provide it with the autocomplete options that are part of the options object
                // and the locations given by the client
                $($input).autocomplete(locations, options.autocompleteOptions)
                .result(function(event, item) {
                        // when the autocomplete function returns a result:
                        //  - trigger a field change event
                        //  - set the location key hidden field
                        //  - handle on location found event
                        
                        fieldChangeEvent();
                        
                        // Set Location Key
                        $("input[name='" + prefix + "_location_key']", parent_form).val(item.key);
                        
                        // Add input-valid class
                        $($input).removeClass(options.invalid_class);
                        $($input).addClass(options.valid_class);
                        if (options.textBlack) {
                            $($input).css('color','#111111');
                        }
                        
                        // Callback function
                        options.onLocationFound(item.lat, item.lng, $input, item.name);
                });
            }
            
            // Attach keyup event to field to be able to tell when something in the field has changed
            // using the keyup event and the fieldChangeEvent function, we basically implement our own
            // onChange event because the onChange event was giving us problems cross browser. To do this,
            // everytime there is a key up event, we check whether the current field value is the same
            // as last time we checked.
            $($input).keyup(fieldChangeEvent);
            function fieldChangeEvent() {
                var tempValue = $($input).val();
                if (tempValue != fieldValue) {
                    // Field value has changed.
                    // Trigger event
                    options.onFieldChange();
                    fieldValue = tempValue;
                            
                    // Only clear hidden fields if input val doesnt equal hidden field val (dont want to erase geocoded work)
                    // if they don't equal eachother, we want to clear the hidden fields because we don't want to keep stuff
                    // in the hidden fields after the user changes what they have typed in.
                    if ($($input).val() != $("input[name='" + prefix + "_full_text']", parent_form).val()) clearHiddenFields();
                }
            }
            
            // function that google geocoder calls upon finishing geocoding
            function geocodeCallback(response, status) {
                // if autocomplete is on, then when the user selects something from the autocomplete dropdown,
                // we don't want to geocode that. To check this, we check whether locations were passed in to
                // locationinput. If there were locations passed in and the field value matches one of the locations
                // and the hidden field for the location key matches the location key of that location, we know that
                // the user picked from the dropdown menu and we shouldn't keep going with the google geocoding.
                if (locations !== null) {
                    if ($("input[name='" + prefix + "_location_key']", parent_form).val()) {
                        for (var i = 0; i < locations.length; i++) 
                        {
                            if ($($input).val() == locations[i].name && locations[i].key == $("input[name='" + prefix + "_location_key']", parent_form).val()) {
                                return false;
                            }
                        }
                    }
                }
                
                // user didn't select this from the dropdown, continue with the geocoding process
                
                // clean slate for hidden fields
                clearHiddenFields();
                
                if (status != google.maps.GeocoderStatus.OK) {
                    // got back bad response. geocoding didnt work.
                    
                    // Add input-invalid class
                    $($input).removeClass(options.valid_class);
                    $($input).addClass(options.invalid_class);
                    if (options.textBlack) {
                        $($input).css('color','#111111');
                    }
                    
                    // onError callback
                    options.onError(status, $input);
                    
                    return false;
                } else {
                    // geocoding worked
                    
                    // Add input-valid class
                    $($input).removeClass(options.invalid_class);
                    $($input).addClass(options.valid_class);
                    if (options.textBlack) {
                        $($input).css('color','#111111');
                    }
                    
                    // use breakout_address function to organize the results returned from google
                    // into the address array
                    var address = breakout_address(response);
                    
                    // Populate Hidden Fields
                    $("input[name='" + prefix + "']", parent_form).val(address['fullText']);
                    $("input[name='" + prefix + "_lat']", parent_form).val(address['lat']);
                    $("input[name='" + prefix + "_lng']", parent_form).val(address['lng']);
                    $("input[name='" + prefix + "_full_text']", parent_form).val(address['fullText']);
                    if (address['country']) {
                        $("input[name='" + prefix + "_country']", parent_form).val(address['country']);
                    }
                    if (address['city']) {
                        $("input[name='" + prefix + "_city']", parent_form).val(address['city']);
                    }
                    if (address['state']) {
                        $("input[name='" + prefix + "_state']", parent_form).val(address['state']);
                    }
                    if (address['zip']) {
                        $("input[name='" + prefix + "_zip']", parent_form).val(address['zip']);
                    }
                    if (address['house_address']) {
                        $("input[name='" + prefix + "_address']", parent_form).val(address['house_address']);
                    }

                    // Callback function
                    options.onLocationFound(address['lat'], address['lng'], $input, address['fullText']);

                }
            }
            
            function clearHiddenFields() {
                // Clear all hidden fields associated with prefix
                $("input[name='" + prefix + "_name']", parent_form).val('');
                $("input[name='" + prefix + "_full_text']", parent_form).val('');
                $("input[name='" + prefix + "_error_code']", parent_form).val('');
                $("input[name='" + prefix + "_address']", parent_form).val('');
                $("input[name='" + prefix + "_city']", parent_form).val('');
                $("input[name='" + prefix + "_state']", parent_form).val('');
                $("input[name='" + prefix + "_zip']", parent_form).val('');
                $("input[name='" + prefix + "_country']", parent_form).val('');
                $("input[name='" + prefix + "_lat']", parent_form).val('');
                $("input[name='" + prefix + "_lng']", parent_form).val('');
                $("input[name='" + prefix + "_location_key']", parent_form).val('');
                $("input[name='" + prefix + "_user_lat']", parent_form).val('');
                $("input[name='" + prefix + "_user_lng']", parent_form).val('');
                $("input[name='" + prefix + "_user_country']", parent_form).val('');
            }
        };
        
        // default options. if any of the options in here are given in the options object passed in by the client, they
        // are overwritten by the client provided options.
        $.locationinput.defaults = {
            // Defaults for options
            onLocationFound: function(latitude, longitude, inputRef, full_text){ return ''; }, // function to be called upon location finding (via geocoding or autocomplete)
            onEmpty: function(inputRef){ return ''; }, // function to be called when the field is emptied
            onError: function(error_code, inputRef){ return ''; }, // function to be called when there is a geocoding error
            onFieldChange: function(){ return ''; }, // function to be called whenever the field changes (though this isn't working completely correctly, see ticket #365)
            loc_values: { // default parameters to set the hidden fields to if locationinput is being called on a prefilled field
                name: '',
                full_text: '',
                error_code: '',
                address: '',
                city: '',
                state: '',
                zip: '',
                country: '',
                lat: '',
                lng:'',
                location_key: '',
                user_lat: '',
                user_lng: '',
                user_country: ''
            },
            autocompleteOptions: { // options to pass onto the autocomplete function
                minChars: 0,
                matchContains: true,
                autoFill: false,
                scroll: false,
                selectFirst: false,
                formatItem: function(row, i, max) {
                    return row.name;
                }
            },
            valid_class: "input-valid", // class to be given to the input field when it has either been geocoded or chosen from the dropdown
            invalid_class: "input-invalid", // class to be given to the input field if there is a geocoding error
            textBlack: false, // make sure to set the text in the field to black. implemented for the search bar because when the user was selecting a location from the dropdown, it was grey instead of black.
            autocompleteOn: true // should autocomplete be turned on?
        };
})(jQuery);

