// ========== GOOGLE MAP CLASS =================================================================================================
var GoogleMap = new Class({
    defaultOptions: {
        showtype: true,
        controlsize: "large", // large or small
        maptype: 0, // [map:0,sat:1,hyb:2]
        zoom: 12,
        minzoom: 3,
        maxzoom: 17,
        showzoom: true,
        showoverview: false,
        smoothzoom: true,
        scrollzoom: false,
        doubleclickzoom: false,
        dragging: true,
        markerList: null,
        initialLoad: true
    },
    params: {
        zoom: 3,
        lat: -95.9766,
        lng: 35.8178,
        minlat: false,
        maxlat: false,
        minlng: false,
        maxlng: false,
        activeMarker: null
    },
    initialize: function(mapID, options){
        this.setOptions($merge(this.defaultOptions, options));
        // ===== Check For Requirements =====
        if (GBrowserIsCompatible()) {
            if (!$(mapID)) 
                return;
            this.mapID = mapID;
            this.map = new GMap($(mapID));
            this.map.setMapType(G_DEFAULT_MAP_TYPES[this.options.maptype]);
            this.map.getPane(G_MAP_FLOAT_SHADOW_PANE).style.display = 'none'; //get rid of bubble shadow
            // ===== Set Options =====
            if (this.options.showtype) 
                this.map.addControl(new GMapTypeControl(3));
            if (this.options.showzoom && this.options.controlsize == "small") 
                this.map.addControl(new GSmallMapControl()); // Small Zoom Controls
            if (this.options.showzoom && this.options.controlsize == "large") 
                this.map.addControl(new GLargeMapControl()); // Large Zoom Controls
            if (this.options.showscale) 
                this.map.addControl(new GScaleControl()) // Map Scale
            if (this.options.showoverview) 
                this.map.addControl(new GOverviewMapControl());
            if (this.options.doubleclickzoom) 
                this.map.enableDoubleClickZoom();
            if (this.options.scrollzoom) 
                this.map.enableScrollWheelZoom();
            if (this.options.smoothzoom) 
                this.map.enableContinuousZoom();
            if (!this.options.dragging) 
                this.map.disableDragging();
            if (this.options.initialLoad) {
                this.initialSetup()
            }
            else {
                this.cachedSetup()
            };
            // ===== Restricting the range of Zoom Levels =====
            // Overwrite the getMinimumResolution() and getMaximumResolution() methods
            var mt = this.map.getMapTypes();
            for (var i = 0; i < mt.length; i++) {
                mt[i].getMinimumResolution = function(){
                    return this.options.minzoom
                }
.bind(this);
                mt[i].getMaximumResolution = function(){
                    return this.options.maxzoom
                }
.bind(this);
            }
            // ===== Add EventListeners To Map =====
            GEvent.addListener(this.map, "dragstart", function(){
                //storeLocator.vars.miles = false
            }
.bind(this));
            GEvent.addListener(this.map, "movestart", function(){
                this.hideBubbleDialog();
            }
.bind(this));
            GEvent.addListener(this.map, "moveend", function(){
                this.zoomSlider.set(this.map.getZoom());
                this.displayMarkers();
            }
.bind(this));
            GEvent.addListener(this.map, "zoomstart", function(){
                this.hideBubbleDialog();
            }
.bind(this));
            GEvent.addListener(this.map, "zoomend", function(){
                this.zoomSlider.set(this.map.getZoom());
            }
.bind(this));
            GEvent.addListener(this.map, "maptypechanged", function(){
                var type = this.map.getCurrentMapType().getName();
                this.adjustMapButtons(type);
            }
.bind(this));
            // ===== Add EventListeners To Graphic Buttons =====
            GEvent.addDomListener($('mapType_MAP'), "click", function(){
                this.map.setMapType(G_NORMAL_MAP);
            }
.bind(this));
            GEvent.addDomListener($('mapType_SAT'), "click", function(){
                this.map.setMapType(G_SATELLITE_MAP);
            }
.bind(this));
            GEvent.addDomListener($('mapType_HYB'), "click", function(){
                this.map.setMapType(G_HYBRID_MAP);
            }
.bind(this));
            
            // ===== Init Slider Zoom Controls =====
            $('btnZoomPlus').addEvent('click', function(){
                this.zoomSlider.set(parseInt(this.params.zoom) + 1);
            }
.bind(this)).setStyle('cursor', 'pointer');
            $('btnZoomMinus').addEvent('click', function(){
                this.zoomSlider.set(parseInt(this.params.zoom) - 1);
            }
.bind(this)).setStyle('cursor', 'pointer');
            /*
             this.zoomSlider = new Slider($('zoomTrack'),$('btnThumb'),{
             start: this.options.minzoom,
             end: this.options.maxzoom,
             offset: 0,
             onComplete: function(pos){
             this.params.zoom = pos;
             this.map.setZoom(parseInt(pos));
             }.bind(this)
             }, null);
             */
            this.zoomSlider = new Slider($('zoomTrack'), $('btnThumb'), {
                range: [this.options.minzoom, this.options.maxzoom],
                snap: true,
                onTick: function(pos){
                    this.knob.setStyle(this.property, pos);
                },
                onComplete: function(pos){
                    this.params.zoom = pos;
                    this.map.setZoom(parseInt(pos));
                }
.bind(this)
            });
            // ===== Marker Array Containers =====
            this.startPoint = null;
            this.gmarkers = []; // full list of markers;
            this.inBounds = []; // track markers in bounds;
            this.htmls = [];
            this.count = 0;
            
            // ===== Directions API ======
            this.gRoute = new GDirections(this.map);
            GEvent.addListener(this.gRoute, "load", function(){
                (function(){
                    this.showDirections()
                }
.bind(this)).delay(1);
            }
.bind(this));
            
            var reasons = []; // === Array for decoding the failure codes ===
            reasons[G_GEO_SUCCESS] = "Success";
            reasons[G_GEO_MISSING_ADDRESS] = "Missing Address: The address was either missing or had no value.";
            reasons[G_GEO_UNKNOWN_ADDRESS] = "Unknown Address:  No corresponding geographic location could be found for the specified address.";
            reasons[G_GEO_UNAVAILABLE_ADDRESS] = "Unavailable Address:  The geocode for the given address cannot be returned due to legal or contractual reasons.";
            reasons[G_GEO_BAD_KEY] = "Bad Key: The API key is either invalid or does not match the domain for which it was given";
            reasons[G_GEO_TOO_MANY_QUERIES] = "Too Many Queries: The daily geocoding quota for this site has been exceeded.";
            reasons[G_GEO_SERVER_ERROR] = "Server error: The geocoding request could not be successfully processed.";
            reasons[G_GEO_BAD_REQUEST] = "A directions request could not be successfully parsed.";
            reasons[G_GEO_MISSING_QUERY] = "No query was specified in the input.";
            reasons[G_GEO_UNKNOWN_DIRECTIONS] = "The GDirections object could not compute directions between the points.";
            
            // === catch Directions errors ===
            GEvent.addListener(this.gRoute, "error", function(){
                var code = this.gRoute.getStatus().code;
                var reason = "Code " + code;
                if (reasons[code]) 
                    reason = reasons[code];
                alert("Failed to obtain directions, " + reason);
            }
.bind(this));
            
        }
        else {
            alert("Sorry, the Google Maps API is not compatible with this browser");
            return;
        };
            },
    // =====[ Data Types ]=======================================================
    loadData: function(data, callback){
        var self = this;
        new Request({
            url: data,
            method: 'get',
            evalScripts: true,
            onComplete: function(text, xml){
                callback(xml);
            }
        }).send();
    },
    // ===== Load Data File ======
    parseXML: function(data){
        var markerList = [];
        var markers = data.getElementsByTagName('marker');
        for (var x = 0, xl = markers.length; x < xl; x++) {
            var marker = markers[x];
            var locale = {
                type: marker.getAttribute('type'),
                icon: icon.display(marker.getAttribute('type')),
                location: marker.getAttribute('location'),
                address: marker.getAttribute('address'),
                hours: marker.getAttribute('hours') || '',
                lat: marker.getAttribute('lat'),
                lng: marker.getAttribute('lng')
            };
            markerList.include(locale);
        };
        map.markerList = markerList;
    },
    initialSetup: function(){
        this.map.setCenter(new GLatLng(this.params.lng, this.params.lat), 3);
    },
    cachedSetup: function(){
        this.map.setCenter(new GLatLng(this.params.lng, this.params.lat), this.params.zoom);
    },
    // =====[ Display Markers ]=====================================================
    resetMap: function(){
        //this.hideBubbleDialog();
        //this.hideMessageDialog();
    },
    updateMap: function(address){
        this.params.address = address;
        this.getLocation(address);
    },
    addLocation: function(lat, lng){
        this.removeAllMarkers();
        var marker = new GMarker(new GLatLng(lng, lat));
        this.gmarkers[this.count] = marker;
        this.map.addOverlay(marker);
        this.setLocation(new GLatLng(lng, lat));
        this.params.lat = lat;
        this.params.lng = lng;
    },
    setLocation: function(point){
        if (!point) {
            var point = new GLatLng(this.params.lng, this.params.lat);
        }
        this.hideBubbleDialog();
        this.map.setCenter(point, this.options.maxzoom);
        //this.setBounds();
    },
    getDirectionsForUser: function(address){
        this.removeAllMarkers();
        var bounds = new GLatLngBounds();
        
        var geocoder = new GClientGeocoder();
        geocoder.getLocations(address, function(result){
            if (result.Status.code == G_GEO_SUCCESS) {
                var place = result.Placemark[0];
                if (place.AddressDetails.Country.CountryNameCode != "US") { // US Locations ONLY
                    this.removeAllMarkers();
                    this.message('OUTSIDE U.S. - Unable to find the location. Please verify the address and submit again.');
                    return false;
                }
                var p = place.Point.coordinates;
                // ====== Set Start Icon =====
                if ($defined(this.startPoint)) 
                    this.removeMarker(this.startPoint)
                var marker = new GMarker(new GLatLng(p[1], p[0]));
                this.startPoint = marker;
                this.map.addOverlay(marker);
                // ===== Set New Boundary to show both User Location and Destination =====
                bounds.extend(new GLatLng(p[1], p[0]));
                bounds.extend(new GLatLng(this.params.lng, this.params.lat));
                this.map.setZoom(this.map.getBoundsZoomLevel(bounds) - 1);
                this.map.setCenter(bounds.getCenter());
                // ===== Show Directions =====
                this.clearDirections();
                this.gRoute.load(address + " to " + this.params.lng + "," + this.params.lat, {
                    'getSteps': true,
                    'preserveViewport': true,
                    'getPolyline': true
                });
            }
            else {
                this.removeAllMarkers();
                this.message('Unable to find the location. Please verify the address and submit again.');
                return false;
            }
        }
.bind(this));
    },
    setBounds: function(){
        var bounds = this.map.getBounds();
        this.params.minlat = Math.min(bounds.getSouthWest().lat(), bounds.getNorthEast().lat());
        this.params.maxlat = Math.max(bounds.getSouthWest().lat(), bounds.getNorthEast().lat());
        this.params.minlng = Math.min(bounds.getSouthWest().lng(), bounds.getNorthEast().lng());
        this.params.maxlng = Math.max(bounds.getSouthWest().lng(), bounds.getNorthEast().lng());
        
        this.saveLocation();
    },
    saveLocation: function(){
        // setCookie;
        //storeLocator.setCookie();
        if ($defined(this.markerList)) {
            this.showMarkers();
        };
            },
    // =====[ Display Markers ]=====================================================
    showMarkers: function(){
        this.createBubbleDialog();
        this.markerList.each(function(mark){
            this.setMarker(mark);
        }
.bind(this));
        this.displayMarkers();
    },
    setMarker: function(mark){
        var marker = new GMarker(new GLatLng(mark.lat, mark.lng), mark.icon);
        this.gmarkers[this.count] = marker;
        this.gmarkers[this.count].type = mark.type.toLowerCase();
        // ===== AddEventListeners =====
        GEvent.addListener(marker, "click", function(){
            this.showBubbleDialog(marker, this.formatDialogText(mark, this.startPoint, marker));
            $('getRoute').addEvent('click', function(e){
                new Event(e).stop();
                this.activeMarker = mark;
                this.getDirections(mark);
                $('getRoute').addClass('loading');
            }
.bind(this));
        }
.bind(this));
        this.count++;
    },
    addMarker: function(marker){
        this.map.addOverlay(marker);
    },
    removeMarker: function(marker){
        this.map.removeOverlay(marker)
    },
    removeAllMarkers: function(){
        if (this.gmarkers.length > 0) {
            while (this.gmarkers.length > 0) {
                this.map.removeOverlay(this.gmarkers.shift());
            }
        }
        this.inBounds = [];
        this.gmarkers = [];
        this.count = 0;
    },
    displayMarkers: function(){
        var total = 0;
        this.inBounds = [];
        // ===== Show markers ONLY if they're in bounds =====
        var bounds = this.map.getBounds();
        var mapBounds = new GLatLngBounds(bounds.getSouthWest(), bounds.getNorthEast());
        this.gmarkers.each(function(marker, i){
            this.removeMarker(marker);
            if (mapBounds.containsLatLng(marker.getPoint())) {
                this.inBounds.include(marker);
                this.addMarker(marker);
                total++;
            };
                    }
.bind(this));
    },
    toggleMarkers: function(type){
        var total = $('totalMarkers').getText();
        this.inBounds.each(function(marker){
            if (marker.type == type) {
                if (!marker.isHidden()) {
                    marker.hide();
                    total--;
                }
                else {
                    marker.show();
                    total++;
                }
            };
                    });
    },
    isMarkerActive: function(type){
        var img = 'chbx_' + type.capitalize();
        return ($(img).getProperty('src').indexOf('_ON') > -1) ? true : false;
    },
    setActiveMarker: function(marker){
        var location = marker.getElement('span').get('html');
        var address = marker.get('html').replace(/<span>(.*?)<\/span>(.*?)/gi, '$2').split(" | ")[0];
        this.activeMarker = {
            location: location,
            address: address
        }
    },
    // =====[ Custom Bubble Dialog ]================================================
    createBubbleDialog: function(){
        if (!$("bubble")) 
            $('googleMap').adopt(new Element('div', {
                'id': 'bubble',
                'class': 'mapBubble'
            }).adopt(new Element('div', {
                'class': 'info'
            })));
    },
    hideBubbleDialog: function(){
        if ($("bubble")) 
            $("bubble").setStyle('display', 'none');
    },
    showBubbleDialog: function(marker, info){
        var pixels = this.positionBubbleTo(marker)
        $("bubble").setStyles({
            'top': pixels.y - 135 + 'px',
            'left': pixels.x - 55 + 'px',
            'display': 'block'
        });
        $E('#bubble .info').setHTML(info);
        $('getRoute').removeClass('loading');
        new Element('div', {
            'class': 'close'
        }).setHTML('<b>x</b> close').addEvent('click', function(){
            this.hideBubbleDialog()
        }
.bind(this)).injectTop($E('#bubble .info'));
    },
    positionBubbleTo: function(marker){
        var cornerTopLeft = this.map.fromContainerPixelToLatLng(new GPoint(0, 0), true);
        var mapPos = this.map.fromLatLngToDivPixel(cornerTopLeft);
        var markerPos = this.map.fromLatLngToDivPixel(marker.getPoint());
        var markerPagePos = new GPoint(markerPos.x - mapPos.x, markerPos.y - mapPos.y);
        var pixels = this.map.getContainer().getParent().getPosition();
        //return {x:markerPagePos.x+pixels.x,y:markerPagePos.y+pixels.y}
        return {
            x: markerPagePos.x,
            y: markerPagePos.y
        }
    },
    // =====[ Alert Message Dialog ]================================================
    createMessageDialog: function(){
        //if(!$("bubble")) $('googleMap').adopt( new Element('div',{'id':'bubble', 'class':'mapBubble'}) );
    },
    hideMessageDialog: function(){
        // if($("bubble")) $("bubble").setStyle('display','none');
    },
    message: function(msg){
        //console.log(msg);
        /*
         var pixels = this.positionBubbleTo(marker)
         $("bubble").setStyles({
         'top': pixels.y-218+'px',
         'left': pixels.x-135+'px',
         'display':'block'
         });
         */
    },
    positionMessageTo: function(marker){
        /*
         var cornerTopLeft = this.map.fromContainerPixelToLatLng(new GPoint(0,0),true);
         var mapPos = this.map.fromLatLngToDivPixel(cornerTopLeft);
         var markerPos = this.map.fromLatLngToDivPixel(marker.getPoint());
         var markerPagePos = new GPoint(markerPos.x-mapPos.x, markerPos.y-mapPos.y);
         //var pixels = cumulativeOffset(this.map.getContainer());
         var pixels = this.map.getContainer().getPosition();
         return {x:markerPagePos.x+pixels.x,y:markerPagePos.y+pixels.y}
         */
    },
    // =====[ Format Strings ]======================================================
    formatDialogText: function(info, start, end){
        var newline = info.address.indexOf(', ') + 1;
        var street = info.address.substring(0, newline);
        var state = info.address.substring(newline + 1, info.address.length);
        var mileage = Math.round(start.getPoint().distanceFrom(end.getPoint()) / 1609.344);
        var str = '' +
        '<b>' +
        info.type.capitalize() +
        ' Location:</b><br />' +
        '<span>' +
        info.location +
        '</span><br />' +
        street +
        '<br />' +
        state +
        '<br /><br />' +
        ((info.hours) ? '<b>Hours:</b><br />' + info.hours + '<br /><br />' : '') +
        'approx. <span id="getDistance">' +
        mileage +
        ((mileage == 1) ? ' mile' : ' miles') +
        '</span><br /><br />' +
        '<a href="#" id="getRoute">Get Directions</a>'
        return str;
    },
    validUSState: function(str){
        // TODO: autocomplete;
    },
    validUSZipCode: function(str){
        /* ==================================================*\
         DESCRIPTION: Validates that a string is a United
         States zip code in 5 digit format or zip+4 format.
         EXAMPLE: 99999 or 99999-9999
         \*===================================================*/
        var oRegExp = /(^\d{5}$)|(^\d{5}-\d{4}$)/;
        return oRegExp.test(str);
    },
    // =====[ External Map Controls ]===============================================
    adjustMapButtons: function(type){
        $('mapType_MAP').setProperty('src', $('mapType_MAP').getProperty('src').replace('_active', '_inactive'));
        $('mapType_SAT').setProperty('src', $('mapType_SAT').getProperty('src').replace('_active', '_inactive'));
        $('mapType_HYB').setProperty('src', $('mapType_HYB').getProperty('src').replace('_active', '_inactive'));
        switch (type) {
            case 'Satellite':
                $('mapType_SAT').setProperty('src', $('mapType_SAT').getProperty('src').replace('_inactive', '_active'));
                break;
            case 'Hybrid':
                $('mapType_HYB').setProperty('src', $('mapType_HYB').getProperty('src').replace('_inactive', '_active'));
                break;
            default:
                $('mapType_MAP').setProperty('src', $('mapType_MAP').getProperty('src').replace('_inactive', '_active'));
                break;
        }
    },
    // =====[ Driving Directions ]================================================
    getDirections: function(start, end){
        this.clearDirections();
        var startAddress = start || this.params.address;
        var endAddress = end || end.lat + "," + end.lng;
        this.gRoute.load("from: " + startAddress + " to: " + endAddress, {
            'getSteps': true,
            'preserveViewport': true,
            'getPolyline': true
        });
    },
    showDirections: function(){
       if($('directions').getParent().hasClass('hide')) $('directions').getParent().removeClass('hide');
        if ($('dirSteps')) 
            $('dirSteps').destroy();
        new Element('table', {
            'id': 'dirSteps'
        }).injectBefore($('directions').getElement('.summary'));
        var tbody = new Element('tbody').injectInside('dirSteps');
        tbody.adopt(new Element('tr').adopt(new Element('th', {
            'colspan': 3
        }).set('html', '<b>' + this.activeMarker.location + '</b><br />' + this.activeMarker.address)));
        // === read through the GRoutes and GSteps ===
        for (var r = 0, rl = this.gRoute.getNumRoutes(); r < rl; r++) {
            var route = this.gRoute.getRoute(r);
            for (var s = 0, sl = route.getNumSteps(r); s < sl; s++) {
                var step = route.getStep(s);
                tbody.adopt(new Element('tr').addClass((s % 2 === 0) ? '' : 'odd').adopt(new Element('td').set('html', '<b>' + (s + 1) + '.</b>')).adopt(new Element('td').set('html', step.getDescriptionHtml())).adopt(new Element('td').set('html', '<em>' + step.getDistance().html + '</em>')));
            }
        }
        // ===== Hide START / STOP / PAUSE icon(s) =====
        var numMarkers = this.gRoute.getNumGeocodes();
        for (var i = 0; i < numMarkers; i++) {
            var marker = this.gRoute.getMarker(i);
            //if (marker != null) marker.hide();
        }
        // ===== Add Path Between Points =====
        this.map.addOverlay(this.gRoute.getPolyline());
        // ===== Mileage / Duration =====
        $E('#directions .summary').set('html', this.gRoute.getSummaryHtml());
        // ===== Copyright =====
        $E('#printDirections .copyright').set('html', this.gRoute.getCopyrightsHtml());
        // ===== Print Function =====
        $E('#printDirections a').addEvent('click', function(e){
            new Event(e).stop();
            this.printDirections();
        }
.bind(this));
        
        $('directions').setStyle('display', 'block');
        $('printDirections').setStyle('display', 'block');
        this.map.setMapType(G_NORMAL_MAP);
        this.hideBubbleDialog();
    },
    clearDirections: function(){
        this.gRoute.clear();
        $E('#directions .summary').set('html', '&nbsp;');
        $E('#printDirections .copyright').set('html', '&nbsp;');
        if ($('dirSteps')) 
            $('dirSteps').destroy();
        if ($('formatForPrint')) 
            $('formatForPrint').destroy();
        $('directions').setStyle('display', 'none');
    },
    printDirections: function(){
        // ===== Format Content for Print =====
        $(document.body).adopt(new Element('div', {
            'id': 'formatForPrint'
        }))
        $('formatForPrint').set('html', $('directions').innerHTML);
        $E('#formatForPrint img').destroy();
        $E('#formatForPrint a').destroy();
        
        // ===== Add Content to popup window =====
        var str = '<html><head><title>Directions</' + 'title>' +
        '<link type="text/css" rel="stylesheet" href="http://www.tribunemediaservices.com/templates/TMSE_directions.css" />' +
        '</' +
        'head><body>' +
        '<div id="formatForPrint">' +
        $('formatForPrint').innerHTML +
        '</div>' +
        '</' +
        'body></' +
        'html>';
        var win = window.open("", "", 'toolbar=no,scrollbars=yes,width=640,height=640');
        win.document.open();
        win.document.write(str);
        win.document.close();
        win.focus();
        setTimeout(function(){
            win.print()
        }, 2000);
    },
    
    // ===== Gather Input Values and Format Address =====
    buildAddress: function(){
        var str = '';
        // ===== values =====
        var address = $E('#mapAddress input').value.clean();
        var city = $E('#mapCity input').value.clean();
        var state = $E('#mapState input').value.clean();
        var zip = $E('#mapZipCode input').value.clean();
        // ===== format =====
        if ((city && state) || this.validUSZipCode(zip)) {
            str += (address) ? address + ' ' : '';
            str += (city) ? city + ', ' : '';
            str += (state) ? state + '. ' : '';
            str += (zip) ? zip : '';
            this.getDirectionsForUser(str);
        }
        else {
            this.resetMap();
            this.message('Please enter a City and State OR a Zip Code.')
        }
    }
});
GoogleMap.implement(new Options);
window.addEvent('unload', GUnload);

// =====[ Google Map ]======================================================================================================
var map = null; // global var
function loadMap(){
    map = new GoogleMap('gmap', {
        showtype: false,
        showzoom: false
    });
    // ===== Add Events to Inputs =====
    $$('#googleMap .inputFields input').each(function(input){
        input.addEvent('keydown', function(event){
            var e = new Event(event);
            if (e.key == 'enter') {
                map.buildAddress();
                e.stop();
            };
                    });
    });
    $('btnSearch').addEvent('click', function(event){
        map.buildAddress();
        new Event(event).stop();
    });
};
