var Mapping = {
    loaded: false,
    geocoder: null,

    load: function()
    {
        if (Mapping.loaded) return;
        Mapping.loaded = true;
    },

    fix: function()
    {
        // Fixes display issues with missing tiles/markers/etc
        $('.brightspark-map').each(function(){
            $(this).brightspark_map('fix');
        });
    },

    ready: function()
    {
        Mapping.load();
        Mapping.fix();

        $(window).unload(function(){
            GUnload();
        });
    },

    initMap: function(map_id, options)
    {
        $("#" + map_id).brightspark_map(options);
    },

    addMarker: function(map_id, options)
    {
        $("#" + map_id).brightspark_map('addMarker', options);
    },

    geocode: function(full_address, callback, param)
    {
        if (Mapping.geocoder == null) Mapping.geocoder = new GClientGeocoder();

        if (param != null)
        {
            $(document.body).data('Mapping-geocode-param', param);
        }

        Mapping.geocoder.getLocations(full_address, function(response)
        {

            if (response.Status.code == 200)
            {
                var results = new Array();
                $.each(response.Placemark, function(i)
                {
                    var result = {
                        country:null,
                        province:null,
                        county:null,
                        city:null,
                        address:null,
                        postalcode:null
                    };
                    var data = this.AddressDetails.Country;

                    result.full_address = this.address;
                    result.accuracy = this.AddressDetails.Accuracy;

                    if (data != undefined)
                    {
                        result.country = data.CountryNameCode;
                        data = data.AdministrativeArea;

                        if (data != undefined)
                        {
                            result.province = data.AdministrativeAreaName;
                            data = data.SubAdministrativeArea;

                            if (data != undefined)
                            {
                                result.county = data.SubAdministrativeAreaName;
                                data = data.Locality;

                                if (data != undefined)
                                {
                                    result.city = data.LocalityName;

                                    if (data.Thoroughfare != undefined)
                                    {
                                        result.address = data.Thoroughfare.ThoroughfareName;
                                    }

                                    if (data.PostalCode != undefined)
                                    {
                                        result.postalcode = data.PostalCode.PostalCodeNumber;
                                    }

                                } // if city
                            } // if county/region
                        } //if province/state
                    } // if country

                    results[results.length] = result;
                });

                callback(results, $(document.body).data('Mapping-geocode-param'));
            }
            else
            {
                callback(null, $(document.body).data('Mapping-geocode-param'));
            }

        });
    },

    getDirections: function(from, to, options)
    {
        var map = null;
        var panel = null;
        var directions = null;

        if ('map' in options)
        {
            map = options.map.brightspark_map('getGMap');
            assert(map != null, 'map is not a valid brightspark map');
        }
        if ('directions' in options)
        {
            panel = $(options.directions);
            panel.empty();
            panel = panel[0];
        }

        directions = new GDirections(map, panel);
        GEvent.addListener(directions, 'load', function(){
            Mapping.fix();
            if ('onload' in options) options.onload();
        });

        if ('onerror' in options) GEvent.addListener(directions, 'error', options.onerror);
        directions.load("from: " + from + " to: " + to);
    }
};

function MapData()
{
    this.div = null;
    this.map = null;
    this.overlays = null;
    this.bounds = new GLatLngBounds();
    this.zoom = 15;

    this.focusMarker = function (pos, marker_info_html)
    {
        this.map.openInfoWindowHtml(pos, marker_info_html);
    };

    this.createMarker = function (lat, lng)
    {
        Mapping.load();

        var marker = null;
        var args = {};
        var pos = new GLatLng(lat, lng);
        var options = {
            draggable:false,
            icon:null
        };

        if (arguments.length > 3) args = arguments[3];

        if ("image" in args)
        {
            options.icon = new GIcon();
            options.icon.image = args.image;
            options.icon.iconSize = new GSize(args.marker_x || 40, args.marker_y || 40);
            options.icon.iconAnchor = new GPoint(args.anchor_x || 12, args.anchor_y || 38);
        }

        marker = new GMarker(pos, options);

        if (("html" in args) && (args.html.length > 0))
        {
            GEvent.addListener(marker, 'click', function() {
                this.focusMarker(pos, args.html);
            });
        }

        this.bounds.extend(pos);

        return marker;
    };

    this.showAll = function ()
    {
        this.map.closeInfoWindow();
        zoom = this.map.getBoundsZoomLevel(this.bounds);
        if (zoom > this.zoom) zoom = this.zoom;
        this.map.setZoom(zoom);
        this.map.setCenter(this.bounds.getCenter());

    };

    this.processOverlays = function ()
    {
        if (this.overlays != null)
        {
            if (this.map != null)
            {
                while (this.overlays.length > 0)
                {
                    this.map.addOverlay(this.overlays.pop());
                }
            }

            this.overlays = null;
            this.showAll();
        }
    };

    this.init = function (div, lat, lng, options)
    {
        Mapping.load();

        this.div = div;
        div.addClass('brightspark-map');

        if (options == null) options = {};

        if (GBrowserIsCompatible())
        {
            this.map = new GMap2(div[0]);

            if ("zoom" in options) this.zoom = options.zoom;
            this.map.setCenter(new GLatLng(lat, lng), this.zoom);

            if (("controls" in options) && (options.controls == "small"))
            {
                this.map.addControl(new GSmallMapControl());
            }

            if (("draggable" in options) && options.draggable)
            {
                this.map.enableDragging();
            }
            else
            {
                this.map.disableDragging();
            }

            if (("zoomable" in options) && options.zoomable)
            {
                this.map.enableDoubleClickZoom();
            }
            else
            {
                this.map.disableDoubleClickZoom();
            }

            GEvent.addListener(
                this.map,
                'infowindowclose',
                function()
                {
                    this.showAll();
                    //google_map.setCenter(new GLatLng(lat, lng));
                }
            );

            this.processOverlays();
        }

    };

    this.addMarker = function (lat, lng, options)
    {
        if (options == null) options = {};

        if (this.overlays == null)
        {
            this.overlays = new Array();
        }

        var marker = this.createMarker(lat, lng, options);

        this.overlays.push(marker);

        this.processOverlays();

        if ("link" in options)
        {
            elem = $(options.link).hover(
                function(){
                    this.focusMarker(marker.getLatLng(), options.html);
                },
                function(){
                    this.showAll();
                }
            )

        }

        var me = this;
        this.div.oneTime('0.2s', function()
        {
            me.fix();
        })

        return marker;
    };

    this.fix = function()
    {
        this.map.checkResize();
        this.showAll();
    };

    this.clearMarkers = function ()
    {
        if (this.map != null)
        {
            this.map.clearOverlays();
        }
    };
};

function AddressPreview(options)
{
    this.options = options;
    this.ignore_change = false;
    this.popup = null;
    this.focused = null;
    this.verified_result = false;
    this.ignore_blur = false;
    this.last_geocoded_address = '';

    this.full_address = function()
    {
        if ('full_address' in options)
        {
            return options.full_address.val();
        }
        else
        {
            var vals = new Array();
            if ('address' in options) vals[vals.length] = options.address.val();
            if ('city' in options) vals[vals.length] = options.city.val();
            if ('province' in options) vals[vals.length] = options.province.val();
            if ('postalcode' in options) vals[vals.length] = options.postalcode.val();
            if ('country' in options) vals[vals.length] = options.country.val();
            return vals.join();
        }
    };

    this.createPopup = function()
    {
        trace('AddressPreview: createPopup');
        this.popup = $(document.createElement('div'));
        this.popup.addClass('brightspark-address-preview-popup');
        this.popup.css({'z-index':'20', 'display':'none', 'position':'absolute'});

        if ('full_address' in this.options)
        {
            this.options.full_address.wrap(
                "<div style='display:inline;position:relative;' class='brightspark-preview-wrapper'></div>"
            );
            var wrapper = this.options.full_address.closest('.brightspark-preview-wrapper');
            wrapper.append(this.popup);
            this.popup.css({
                'top': wrapper.outerHeight(true).toString() + "px",
                'left': '0px'
            });
            this.popup.data('_data', this);
            this.popup.focus(function(){
                $(this).data('_data').refocus();
            })
            this.ignore_blur = true;
        }
        else
        {
            //TODO
        }

    };

    this.showPopup = function(results)
    {
        trace('AddressPreview: showPopup');

        if (this.popup == null) this.createPopup();


        if (results == null)
        {
            this.popup.html('<div class="brightspark-address-preview-nomatch">No matches for this location</div>');
        }
        else
        {
            var list = $(document.createElement('div'));
            var addin = this;

            list.addClass('brightspark-address-preview-list');
            this.popup.empty();
            this.popup.append(list);

            $.each(results, function(index)
            {
                var item = $(document.createElement('div'));
                item.addClass('brightspark-address-preview-list-item');
                if (index == 0) item.addClass('selected');
                item.data('addressPreview', addin);
                item.data('geocodeResult', this);
                item.text(this.full_address);
                list.append(item);

                item.click(addin.onselect);
            });

        }

        this.popup.show();
        this.refocus();
    };

    this.hidePopup = function()
    {
        if (this.popup != null) this.popup.hide();
    };

    this.callback = function(results, param)
    {
        if (results == null)
        {
            trace("address_preview: no results");
        }
        else
        {
            $.each(results, function(i)
            {
                trace("address_preview: result(" + i + "): " + this.full_address);
            });
        }

        param.showPopup(results);
    };

    this.selectItem = function(item)
    {
        var result = $(item).data('geocodeResult');

        this.verified_result = true;
        this.options.full_address.val(result.full_address);
    };

    this.refocus = function()
    {
        if (this.focused != null)
        {
            this.focused[0].focus();
            this.focused.val(this.focused.val()); // Forces caret to end of text
        }
    };

    this.onselect = function()
    {
        trace('AddressPreview: onselect');

        var data = $(this).data('addressPreview');

        data.selectItem(this);
        data.hidePopup();
    };

    this.onchange = function(event)
    {
        trace('AddressPreview: onchange');
        trace('keycode = ' + event.keyCode);
        var data = $(this).data('addressPreview');
        if (data.ignore_change) return;

        if ((event.keyCode == 38) || (event.keyCode == 40)) // cursor up or down
        {
            event.preventDefault();
            event.stopPropagation();
            if (data.popup.css('display') == 'none') return;

            var selected = data.popup.find('.selected');
            var new_item = (event.keyCode == 38) ? selected.prev() : selected.next();

            if (new_item.size() > 0)
            {
                new_item.addClass('selected');
                selected.removeClass('selected');
            }
        }
        else
        {
            var val = data.full_address();
            if (val != data.last_geocoded_address)
            {
                data.verified_result = false;
                trace('AddressPreview: geocoding = ' + val);
                Mapping.geocode(val, data.callback, data);
                data.last_geocoded_address = val;
            }
        }
    };

    this.onblur = function()
    {
        trace('AddressPreview: onblur');
        var data = $(this).data('addressPreview');

        if (data.ignore_blur == true)
        {
            trace('... ignored')
            data.ignore_blur = false;
            data.refocus();
        }
        else if (data.verified_result == false)
        {
            var first = data.popup.find('.brightspark-address-preview-list-item.selected');

            if (first.length > 0)
            {
                data.selectItem(first);
            }

            $(this).oneTime(500, function(){ data.hidePopup(); });
        }
    };

    this.hookup = function(ctl)
    {
        if (ctl[0].nodeName == 'INPUT')
        {
            ctl.attr('autocomplete', 'off');
        }

        ctl.data('addressPreview', this);
        ctl.keyup(this.onchange);
        ctl.blur(this.onblur);
        ctl.focus(function()
        {
            $(this).data('addressPreview').focused = $(this);
        });
    };

    this.init = function()
    {
        trace('AddressPreview: init');

        if ('full_address' in this.options) this.hookup(this.options.full_address);
        if ('address' in this.options)      this.hookup(this.options.address);
        if ('city' in this.options)         this.hookup(this.options.city);
        if ('province' in this.options)     this.hookup(this.options.province);
        if ('postalcode' in this.options)   this.hookup(this.options.postalcode);
        if ('country' in this.options)      this.hookup(this.options.country);
    };

    this.init();
};

$.fn.extend({
    brightspark_map: function(method, options)
    {
        var data = this.data('mapData');

        if (typeof(method) == 'object')
        {
            options = method;
            method = 'create';
        }

        if (method == 'create')
        {
            data = new MapData();
            data.init(this, options.lat, options.lng, options);
            this.data('mapData', data);
        }
        else if (data == null)
        {
            return null;
        }
        else if (method == 'addMarker')
        {
            data.addMarker(options.lat, options.lng, options);
        }
        else if (method == 'clear')
        {
            data.clearMarkers();
        }
        else if (method == 'getGMap')
        {
            return data.map;
        }
        else if (method == 'fix')
        {
            data.fix();
        }
    }
});

$.extend({
    address_preview: function(options)
    {
        new AddressPreview(options);
    }
});

$(document).ready(Mapping.ready);
