/*
 * API Extensions for Google Maps
 *
 */
var RiderGExtensions = new Class({

    __version__:     '0.0.1',

    __doc__:         'API Extensions for Google Maps',

    initialize: function(){

        if($defined(this.exists))
            return;

        this.exists     = true;

        var _ns         = google.maps;                
        var latlng      = _ns.LatLng;
        var bounds      = _ns.LatLngBounds;
        var point       = _ns.Point;
        var marker      = _ns.Marker;

        var poly        = _ns.Polyline;
        var polygon     = _ns.Polygon;


        /** 
         * Returns: the 2 LatLng added
         * Example:
         *  pos1.add( pos2 ) or the form pos1.add( _lat, _lng )
         */
        latlng.prototype['add'] = function(){
            switch(arguments.length){
                case 1:
                    _u = arguments[0].lng();
                    _v = arguments[0].lat();
                    break;
                case 2:
                    _u = arguments[0];
                    _v = arguments[1];
                    break;

                default:
                    return this;
            }
            return new latlng( this.lng() + _u, this.lat() + _v );
        };

        latlng.prototype['mult'] = function(_u){
            return new latlng( this.lng() * _u, this.lat() * _u );
        };

        /**
         * Encode and decode a lat/lng
         * You may specify the precision. Default is 6 digits 
         */
        latlng.prototype['encode'] = function(precision){
            var p    = precision || 6;

            var clat = (parseInt(this.lat() * (Math.pow(10,p)))).toString(32);
            var clng = (parseInt(this.lng() * (Math.pow(10,p)))).toString(32);

            return [clat, clng];
        },

        latlng.prototype['decode'] = function(encoded,precision){
            var p = precision || 6;

            var lat = (encoded[0].toInt(32) / Math.pow(10,p));
            var lng = (encoded[1].toInt(32) / Math.pow(10,p));

            return new latlng( lat, lng );
        },

        /**
         * Given two points, calculate the PLANAR distance between them.
         * Note: this algorithm is not suited for accuracy, just for
         * Comparing distances. Delta Epsilon ~= 1.0824 in worst-case
         *
         * Code taken from Graphic Gems book: pg 429
         * |x| + |y| - min(|x|,|y|)/2
         *
         * @note Ok, this is worthless if comparing small distances (like... 10km)
         * @seealso distanceFrom , dist
         */
        latlng.prototype['fastDist'] = function( point2 ){
            var x1 = this.lng();
            var y1 = this.lat();
            var x2 = point2.lng();
            var y2 = point2.lat();

            // Modulo
            if( (x2-x1) < 0 ) x2 = -x2;
            if( (y2-y1) < 0 ) y2 = -y2;

            //     x + y -    1/2 * min(|x|,|y|)
            return x2+y2 - ((x2>y2 ? y2 : x2) << 1);
        };

        /**
         * Given two points, calculate the PLANAR distance between them.
         * Does not account for earth roundiness.
         *
         * @seealso distanceFrom, fastDist
         */
        latlng.prototype['dist'] = function( point2 ){
            var x1 = this.lng();
            var y1 = this.lat();
            var x2 = point2.lng();
            var y2 = point2.lat();

            return Math.sqrt( (x2-x1)*(x2-x1) + (y2-y1)*(y2-y1) );
        };

        /**
         * Return a normal point that (originalPoint dot normalPoint) = 0,
         *           or in other words, is perpendicular to originalPoint.
         *            
         *
         * **Carefull** -  The point is assumed as a planar 2D vector
         */
        latlng.prototype['getNormal'] = function(){
            var u = [ this.lng(), this.lat(), 0 ];    // Plane z=0J
            var v = [ 0, 0, 1 ];                      // Plane x=0

            // Cross Product
            var k = [ u[1]*v[2] - u[2]*v[1], u[2]*v[0] - u[0]*v[2], u[0]*v[1] - u[1]*v[0] ];
            return new latlng( k[0], k[1] , k[2] );
        };
        
        /**
         * Given two points, returns the Bounding box
         *
         */
        latlng.prototype['bb'] = function( point2 ){
            var x1 = this.lng();
            var y1 = this.lat();
            var x2 = point2.lng();
            var y2 = point2.lat();

            with(Math)
                return new google.maps.LatLngBounds( new latlng( min(x1,x2), min(y1,y2) ), new latlng( max(x1,x2), max(y1,y2) ) );
        };

        /*
         * Create a new circle poly of the given radius an quality
         */
        latlng.prototype['circlePoly'] = function( radius, quality , liColor, liWidth, liOpa, fillColor, fillOpa ){

            var center = new latlng( this.lat(), this.lng() );

            //calculating km/degree
            var latConv = center.distanceFrom(new GLatLng(center.lat()+0.1, center.lng()))/100;
            var lngConv = center.distanceFrom(new GLatLng(center.lat(), center.lng()+0.1))/100;

            //Loop 
            var points = [];
            var step = parseInt(360/quality) || 10;
            for(var i=0; i<=360; i += step){
                var pint = new GLatLng(center.lat() + (radius/latConv * Math.cos(i * Math.PI/180)), center.lng() + 
                (radius/lngConv * Math.sin(i * Math.PI/180)));
                points.push(pint);
                bounds.extend(pint); //this is for fit function
            }

            points.push(points[0]); // Closes the circle, thanks Martin
            fillColor = fillColor||liColor||"#0055ff";
            liWidth   = liWidth||2;

            var poly = new GPolygon(points,liColor,liWidth,liOpa,fillColor,fillOpa);

            return poly;
        };

        latlng.prototype['intersect'] = function( line1, line2 ){
            var p0 = new GPoint( line1[0].lng() , line1[0].lat() );
            var p1 = new GPoint( line1[1].lng() , line1[1].lat() );
            var p2 = new GPoint( line2[0].lng() , line2[0].lat() );
            var p3 = new GPoint( line2[1].lng() , line2[1].lat() );

            return GPoint.intersect( p0, p1, p2, p3 ); 
        };

        /*
         * Compute the intersection(point) of two lines. 
         *
         * Instead of using a line class, i will do a hack here:
         *   line1 and line2 is a tuple of two elements.
         *      This tuple contains 2 points( p0,p1 ), which represent
         *      a line.
         *
         * Returns the intersection point of the two lines
         * http://mathforum.org/library/drmath/view/53254.html
         */
        point.prototype['intersect'] = function( p0, p1, p2, p3 ){

            var a1,b1,c1;   // Coefficients of line 1 
            var a2,b2,c2;   // Coefficients of line 1 
            var m1,m2;      // Slopes 

            var epsilon = 1e+10;

            var x0      = p0.x;         var y0          = p0.y;
            var x1      = p1.x;         var y1          = p1.y;
            var x2      = p2.x;         var y2          = p2.y;
            var x3      = p3.x;         var y3          = p3.y;

            // Compute the slopes
            if((x1-x0) != 0)        m1 = (y1-y0)/(x1-x0)
            else                    m1 = epsilon; 

            if((x3-x2) != 0)        m2 = (y3-y2)/(x3-x2);
            else                    m2 = epsilon;

            // They are parallel!
            //if(m1 == m2)
            //    return null;

            // Let's finally create our linear equations
            a1 = m1; a2 = m2;
            b1 = -1;
            b2 = -1;
        
            c1 = (y0-m1*x0);
            c2 = (y2-m2*x2);

            // Cramer to rescue us and solve the system! 
            var inv_det = 1/(a1*b2 - a2*b1); 
            var xi      = ((b1*c2 - b2*c1) * inv_det );
            var yi      = ((a2*c1 - a1*c2) * inv_det );

            return new google.maps.Point(Math.round(xi),Math.round(yi));
        };

        /** 
         * 
         * Given a LatLngBounds, create a Polygon of the Bounding Box
         *
         */
        bounds.prototype['getPolygon'] = function( skin ){
            var p1 = this.getSouthWest();
            var p2 = this.getNorthEast();

            var xoff = p2.lat() - p1.lat();
            var yoff = p2.lng() - p1.lng();

            var polygon = new google.maps.Polygon([
                new GLatLng(    p1.lat(),           p1.lng()        ),
                new GLatLng(    p1.lat() + xoff,    p1.lng()        ),
                new GLatLng(    p1.lat() + xoff,    p1.lng() + yoff ),
                new GLatLng(    p1.lat(),           p1.lng() + yoff ),

                new GLatLng(    p1.lat(),           p1.lng()        )
            ], skin.stroke_color, skin.stroke_width , skin.stroke_opacity, skin.fill_color, skin.fill_opacity );
          
            return polygon;
        };

        polygon.prototype['toJson'] = function( ){
            var json = [];

            var j = this.getVertexCount();
            for(var i=0; i < j; i++){
                var p = this.getVertex(i);
                json.push( [ p.lat() , p.lng() ] ); 
            }

            return json;
        };

        poly.prototype['toJson'] = function( ){
            var json = [];

            var j = this.getVertexCount();
            for(var i=0; i < j; i++){
                var p = this.getVertex(i);
                json.push( [ p.lat() , p.lng() ] ); 
            }

            return json;
        };

        /*
         * Given a Polyline, current zoom and radius, get the general enclosing polygon with
         * the specified radius. The polygon is a array of LatLng
         */
        poly.prototype['getEnclosingPolygon'] = function( map, zoom, radius_in_px ){

            var offset            = radius_in_px;
            var proj              = map.getCurrentMapType().getProjection(); 
            var searchAreaPoints  = {'TOP': new Array(), 'BOTTOM': new Array()};

            // Variables used as lat point in the loop
            var c_p1top, c_p2top;
            var c_p1bot, c_p2bot;
            var c_theta;

            var vertexCount = this.getVertexCount();
            for(var i=1; i < vertexCount; ++i){

                // Current line
                var p1      =   proj.fromLatLngToPixel( this.getVertex(i-1), zoom );
                var p2      =   proj.fromLatLngToPixel( this.getVertex(i)  , zoom );

                var dist    =   Math.sqrt( ((p1.x-p2.x)*(p1.x-p2.x))+((p1.y-p2.y)*(p1.y-p2.y)) );  
                var theta   =   Math.atan2( p1.x - p2.x , p1.y - p2.y);
                var nTheta  =   theta + (Math.PI/2); 
                if( theta > Math.PI ) theta -= Math.PI*2;

                // Offset(radius) in each direction perpendicular direction 
                var dx      =   offset * Math.sin( nTheta );  
                var dy      =   offset * Math.cos( nTheta );

                // Four points parallel to the current line.
                // Distance between parallel line and current one, is.... RADIUS!
                //
                // p1top            p2top
                //  |                |
                //  |                |
                // p1----------------p2
                //  |                |
                //  |                |
                // p1bot            p2bot
                //
                var p1top   =   new google.maps.Point( p1.x + dx,   p1.y + dy );
                p2top       =   new google.maps.Point( p2.x + dx,   p2.y + dy );
                var p1bot   =   new google.maps.Point( p1.x - dx,   p1.y - dy );
                p2bot       =   new google.maps.Point( p2.x - dx,   p2.y - dy );

                // Mid point of the list. Branch-predicting friendly 
                if(i != 1 && nTheta != c_theta){

                     // Calculate the intersection point of current line and last line!
                     var x_top = new google.maps.Point(0,0).intersect( c_p1top, c_p2top, p1top , p2top );
                     var x_bot = new google.maps.Point(0,0).intersect( c_p1bot, c_p2bot, p1bot , p2bot ); 

                     var x_topDist      = new google.maps.Point( x_top.x - p1.x , x_top.y - p1.y);
                     var x_botDist      = new google.maps.Point( x_bot.x - p1.x , x_bot.y - p1.y);
                     var x_dist         = Math.sqrt( (x_topDist.x*x_topDist.x) + (x_topDist.y*x_topDist.y) );
                     //var x_dist         = Math.sqrt( (x_botDist.x*x_botDist.x) + (x_botDist.y*x_botDist.y) );
                     var x_normalize    = offset / x_dist;

                     var dTheta         = nTheta - c_theta;
                     //if(dTheta < (Math.PI*2)) dTheta += Math.PI*2;
                     //if(dTheta > (Math.PI*2)) dTheta -= Math.PI*2;

                     if( dTheta < Math.PI){
                         var s = x_normalize;

                        // Calculate a bend point
                        // when the angle is too extreme (if angle is closed and small, this means that intersection point is.... 
                        // REALLY FAR AWAY!)
                        //
                        if(dTheta > 0){
                            searchAreaPoints['TOP'].push(    c_p2top );
                            searchAreaPoints['TOP'].push(    new google.maps.Point( p1.x + (s*x_topDist.x) , p1.y + (s*x_topDist.y) ));
                            searchAreaPoints['TOP'].push(    p2top );
                        }
                        else{
                            searchAreaPoints['TOP'].push(    x_top );
                        }

                     }else if( x_dist <  dist ){
                        searchAreaPoints['TOP'].push (   x_top );
                     } else {
                       searchAreaPoints['TOP'].push(    c_p2top );
                       searchAreaPoints['TOP'].push(    p1top);
                     }

                // First point of the list or slope didn't change!
                } else {
                    searchAreaPoints['TOP'].push(    p1top );
                    searchAreaPoints['BOTTOM'].push( p1bot );
                }

                // Walk! Continues the loop
                c_p1top = p1top;
                c_p1bot = p1bot;
                c_p2top = p2top;
                c_p2bot = p2bot;
                c_theta = nTheta;
            }

            // Finally, the last point
            searchAreaPoints['TOP'].push(    p2top ); 
            searchAreaPoints['BOTTOM'].push( p2bot ); 

            // Lets convert everything back to lat/lng
            searchAreaPoints['TOP'].each( function(point,ix){
                //var cPoint = new google.maps.Point( point.x + (Math.cos(generalRouteSlope + (Math.PI/2)) * radius), point.y + (Math.sin(generalRouteSlope + (Math.PI/2)) * radius) );
                searchAreaPoints['TOP'][ix] = proj.fromPixelToLatLng( point , zoom );
            });

            var fullPolygon = searchAreaPoints['TOP'];
            return fullPolygon;

            // Include BOTTOM points in reverse order
            //searchAreaPoints['BOTTOM'].each( function(item,ix){
            //    fullPolygon.push( searchAreaPoints['BOTTOM'][ mainPoints_len - (ix+1) ] ); 
            //});
            //return fullPolygon;
        };

        /*
         * Simplify a polygon using Douglas-peucker algorithm.
         */

        /*
         * Simplify a Polygon using some shitty algorithm.
         * Epsilon is the aggressivity of the algorithm, beware modafoques!
         */
        poly.prototype['simplify2'] = function( epsilon ){
            var simplifiedPolygon = new Array();
            var len               = this.getVertexCount();

            var currPoint         = null;
            var previousPoint     = new google.maps.Point(0,0);
            for(var i=0; i < len; ++i){
                currPoint   =   this.getVertex(i);

                if(currPoint.distanceFrom( previousPoint) > epsilon ){
                    simplifiedPolygon.include( currPoint );
                    previousPoint = currPoint;
                }
            }

            if(simplifiedPolygon.getLast() != this.getVertex(len-1))
               simplifiedPolygon.include( this.getVertex(len-1) );

            return simplifiedPolygon;
        };

        /** 
         * Marker Animation extensions
         */
        marker.prototype['inAnim'] = function( ){
            return;
        };

		marker.prototype['hide'] = function() {  
			if (this.getPoint().lat() < 90) {  
				try {  
					this.savePoint = this.getPoint();  
					this.setPoint(new GLatLng(90, 0));  
				} catch (e) { }  
			}  
		};  

		// the show() method puts the marker back in the original position
		marker.prototype['show'] = function() {  
			if (this.getPoint().lat() == 90) {  
				if (this.savePoint) {  
					try {  
						this.setPoint(this.savePoint);  
						this.savePoint = null;  
					} catch (e) { }  
				}  
			}  
		};
		
		// the isHidden() method checks if the marker is put outside the map and if it does
		// returns true
		marker.prototype['isHidden'] = function() {  
			if (this.getPoint().lat() == 90) {  
				return true;  
			} else {  
				return false;  
			}  
		};

        /*
         * This hack avoids google querying it's own server.
         * It saves lil time, but the main feature is that we
         * can shutup non-https warnings! Full https gmaps on the way!
         * 
         * #Issue 917
         * http://code.google.com/p/gmaps-api-issues/issues/detail?id=917
         */
        google.maps.GVerify = function(){};

    }
});

/*
 * Create default icons
 */

GRINGO_DEFAULT_DRAGGER_ICON                 = new google.maps.Icon()
GRINGO_DEFAULT_DRAGGER_ICON.printImage      = '/media/rider/markers/gmarker/dragMarker/printImage.gif';
GRINGO_DEFAULT_DRAGGER_ICON.mozPrintImage   = '/media/rider/markers/gmarker/dragMarker/mozPrintImage.gif';
GRINGO_DEFAULT_DRAGGER_ICON.transparent     = '/media/rider/markers/gmarker/dragMarker/transparent.png';

GRINGO_DEFAULT_DRAGGER_ICON.iconSize         = new google.maps.Size( 46, 44 );  
GRINGO_DEFAULT_DRAGGER_ICON.shadowSize       = new google.maps.Size( 68, 44);
GRINGO_DEFAULT_DRAGGER_ICON.iconAnchor       = new google.maps.Point( 0, 44 );  
GRINGO_DEFAULT_DRAGGER_ICON.infoWindowAnchor = new google.maps.Point( 23, 0 );  
GRINGO_DEFAULT_DRAGGER_ICON.imageMap = [22,0,27,1,31,2,33,3,35,4,36,5,38,6,39,7,40,8,40,9,41,10,42,11,42,12,43,13,43,14,44,15,44,16,44,17,44,18,44,19,45,20,45,21,45,22,45,23,45,24,44,25,44,26,44,27,44,28,43,29,43,30,42,31,42,32,41,33,41,34,40,35,39,36,38,37,37,38,35,39,34,40,31,41,27,42,6,43,4,43,3,42,4,41,0,40,0,39,1,38,1,37,2,36,2,35,2,34,3,33,3,32,4,31,4,30,4,29,3,28,3,27,2,26,2,25,2,24,2,23,1,22,1,21,1,20,1,19,1,18,1,17,1,16,2,15,2,14,2,13,3,12,3,11,3,10,4,9,5,8,6,7,6,6,7,5,9,4,10,3,12,2,14,1,19,0];


/*
 * Feature Icon
 */
GRINGO_DEFAULT_FEATURE_ICON                 = new google.maps.Icon();
GRINGO_DEFAULT_FEATURE_ICON.printImage      = '/media/rider/markers/gmarker/featureMarker/printImage.gif';
GRINGO_DEFAULT_FEATURE_ICON.mozPrintImage   = '/media/rider/markers/gmarker/featureMarker/mozPrintImage.gif';
GRINGO_DEFAULT_FEATURE_ICON.transparent     = '/media/rider/markers/gmarker/featureMarker/transparent.png';

GRINGO_DEFAULT_FEATURE_ICON.iconSize         = new google.maps.Size( 36, 30 );  
GRINGO_DEFAULT_FEATURE_ICON.shadowSize       = new google.maps.Size( 51, 30 );
GRINGO_DEFAULT_FEATURE_ICON.iconAnchor       = new google.maps.Point( 36, 15 );  
GRINGO_DEFAULT_FEATURE_ICON.infoWindowAnchor = new google.maps.Point( 0, 15 );  
GRINGO_DEFAULT_FEATURE_ICON.imageMap = [25,0,27,1,27,2,27,3,27,4,27,5,27,6,27,7,27,8,27,9,27,10,27,11,27,12,27,13,27,14,28,15,29,16,31,17,33,18,34,19,30,20,27,21,27,22,27,23,27,24,27,25,27,26,27,27,27,28,25,29,2,29,1,28,0,27,0,26,0,25,0,24,0,23,0,22,0,21,0,20,0,19,0,18,0,17,0,16,0,15,0,14,0,13,0,12,0,11,0,10,0,9,0,8,0,7,0,6,0,5,0,4,0,3,0,2,1,1,2,0];


/*
 * Rewrite some methods of google maps to allow
 * non-planned behaviour
 */
var RiderGHacks = new Class({
    __version__:        '0.0.1',
    __author__:         'Bob esponja calca quadrada',
    __doc__:            'Google maps API Hacks',

    initialize: function(){
        if($defined(this.exists))
            return this;

        this.exists  = true;
    },

    hackZoom: function(min_zoom, max_zoom){
        var minZoomLimit = function(){
            return min_zoom;
        };

        var maxZoomLimit = function(){
            return max_zoom;
        };

        G_PHYSICAL_MAP.getMinimumResolution     = minZoomLimit; 
        G_NORMAL_MAP.getMinimumResolution       = minZoomLimit; 
        G_SATELLITE_MAP.getMinimumResolution    = minZoomLimit; 
        G_HYBRID_MAP.getMinimumResolution       = minZoomLimit; 

        G_PHYSICAL_MAP.getMaximumResolution     = maxZoomLimit; 
        G_NORMAL_MAP.getMaximumResolution       = maxZoomLimit; 
        G_SATELLITE_MAP.getMaximumResolution    = maxZoomLimit; 
        G_HYBRID_MAP.getMaximumResolution       = maxZoomLimit; 
    },

	makeCopyrightSmaller: function(){

		var gmap = $E('#gmaps-holder').retrieve('map_api');

		// Search for (c) copyright symbol
		for(var i = 0; i < gmap.map.getContainer().childNodes.length; ++i){
			if(gmap.map.getContainer().childNodes[i].innerHTML.indexOf(String.fromCharCode(169)) !== -1){
				gmap.map.getContainer().childNodes[i].style.fontSize = '9px';
				break;
			}
		}
	}

});

/*
 * GringoGControl changes the default control UI
 * of google maps (the zoom bar, mainly!)
 *
 */

function RiderGControl(){
    this.whereami      = null;
    this.htmlContainer = null;

    return;
};

RiderGControl.prototype = new GControl();
RiderGControl.prototype.initialize = function(map){

		var container       = document.createElement('div');
		var sliderContainer = document.createElement('div');
		var uiSlider        = document.createElement('div');
		var whereamiBox     = document.createElement('div');

		var uiZoomTopButton 	= document.createElement('a');
		var uiZoomBottomButton 	= document.createElement('a');

		sliderContainer.id 	  = 'sliderContainer';
		uiSlider.id           = 'zoomSlider';
		whereamiBox.id        = 'whereami';

		uiZoomTopButton.id    = 'sliderTopButton';
		uiZoomBottomButton.id = 'sliderBottomButton';
		uiZoomTopButton['href'] = uiZoomBottomButton['href'] = '#';

		var geo = new RiderGeoLocate(null);
		if(geo.supportGeoApi())
			container.appendChild( whereamiBox );

		sliderContainer.appendChild( uiZoomTopButton );
		sliderContainer.appendChild( uiSlider );
		sliderContainer.appendChild( uiZoomBottomButton );

		container.appendChild( sliderContainer );
		map.getContainer().appendChild( container );

		/* Create the slider plz. */
		$("#zoomSlider").slider({
				orientation: "vertical",
				min: 13,
				max: 18,
				value: 13,
				step: 1,
				animate: true,
				slide: function(event, ui) {
					map.setZoom( ui.value );
				}
		});

		// The buttons, also!
		$("#sliderTopButton").bind('click', function(){
			var cValue = $('#zoomSlider').slider('value');

			var gmap = $E('#gmaps-holder').retrieve('map_api');
			gmap.map.setZoom( cValue + 1);

			//$('#zoomSlider').slider('value', cValue + 1);
		});
		
		$("#sliderBottomButton").bind('click', function(){
			var cValue = $('#zoomSlider').slider('value');

			var gmap = $E('#gmaps-holder').retrieve('map_api');
			gmap.map.setZoom( cValue - 1 );

		});

		this.htmlContainer = container;
		this.whereami      = whereamiBox;

		return container;
};

RiderGControl.prototype.getDefaultPosition = function() {
    return new GControlPosition(G_ANCHOR_TOP_LEFT, new GSize(15, 110));
};

RiderGControl.prototype.increase = function(){
	var cValue = $('#zoomSlider').slider('value');
	$('#zoomSlider').slider('value', cValue + 1);	
};
RiderGControl.prototype.decrease = function(){
	var cValue = $('#zoomSlider').slider('value');
	$('#zoomSlider').slider('value', cValue - 1);	
};
















































/*
 * Rider GoogleMaps Class with mootools
 * Implements Options and Events 
 *
 * Vitor Alves Calejuri <ondeosfrangosnaotemvez@gmail.com>
 *
 *  Dependencies:
 *   Moootols core 1.2 + Mootools more (Assets Class)
 *
 */

var RiderGMap = new Class({
    __version__: '0.0.8',
    __author__:  'Bob esponja calca quadrada',
    __doc__:     'Take off some bullshit and bugs from GMaps API and make it simpler to use',

    Implements: [ Options, Events, Chain ] ,

    /* 
     * Important fields for your callbacks
     */
    map:            null,           /* GMaps Map Object */
    dir:            null,           /* GMaps direction object */ 
    route:          null,           /* The active route (RiderGRoute) */ 
    geo:            null,
    control:        null,
    
    /* Markers */
    markerClusterer:     null,             /* With clustering */
    featureManager:      null,

    // Default... 
    user:              {'lat': -23.578934, 'lng': -46.67593, 'zoom': 12, 'city': 'São Paulo' , 'addr': null},

    /*
     * Options
     */
    options: {
        /* These 2 elements are required (always!) */
        map_el:             '#gringo_map',
        direction_el:       '#gringo_directions',

        /* Styleshit */
        StylesheetAsset: {
            uri:         '/media/gss/gringo.gss',
            ready:       false                         
        },

        AppAsset: {
            uri:         '/media/app/application.json',
            ready:       false 
        }
    },

    // #Check http://code.google.com/apis/maps/documentation/reference.html#GGeoStatusCode  
    status_code: {
        200:     "",
        400:     "",
        500:     "",
        601:     "",
        602:     "Que pena, não conseguimos encontrar o lugar que você procurou. Tente ser mais específico: Rua - Bairro",
        603:     "Que pena, não conseguimos encontrar o lugar que você procurou. Tente ser mais específico: Rua - Bairro",
        604:     "",
        610:     "Que pena, não conseguimos encontrar o lugar que você procurou. Tente ser mais específico: Rua - Bairro",
        620:     "Por favor. Espere um pouco que já vamos mostrar seu resultado."
    },

    /*
     * Methods
     */
    initialize: function( _options ){
        this.ready      = false;
        this.setOptions( _options );

        // Load the GeoStylesheet
        var assets = new RiderAssets();
        assets.load( { 'name':      'gss',
                       'uri':       this.options.StylesheetAsset.uri,
                       'onSuccess': function( gss ){
                            this.gss = gss;

                            this.options.StylesheetAsset.ready      = true;  

                            if(this.options.AppAsset.ready )
                                this.fireEvent('onSuccessLoad');
                       }.bind(this)});

        // Load application configuration 
        assets.load( {'name':       'app',
                      'uri':        this.options.AppAsset.uri,
                      'onSuccess':  function( app ){
                            this.app        = app;

                            this.options.AppAsset.ready      = true;

                            if(this.options.StylesheetAsset.ready) 
                                this.fireEvent('onSuccessLoad');
                        }.bind(this)});        
    },

    /* 
     * Tries to get the user location (lat/lng and city!).
	 * If browser supports GeoApi (and force is enabled), we use
	 * GeoApi (attention, it's asynchronous[onGrantedUserLocation]). If the browser
	 * doesn't support, we use google system to match ip/LatLng
	 *
     */                
    getClientLocation: function( force ){

        var location_api = new RiderGeoLocate(); 
                                              
        if( force && location_api.supportGeoApi()){

			// GeoAPI Success
            var permissionGranted = function( position ){
                this.user.lat   = position.coords.latitude;
                this.user.lng   = position.coords.longitude;

                // Some Browsers give me this info... some not...
				var fillCity = function( georesponse ){
					if (!georesponse || georesponse.Status.code != 200){ 
						this.fireEvent( 'onUserLocationFatalError', georesponse );
						return;
					}
					
					var place = georesponse.Placemark[0];
					if(place.AddressDetails.Accuracy >= 4){         // Accuracy 4 is CITY level. Bigger accuracy is better.
						this.user.addr = place.address.split(',')[0];
						this.user.city = place.AddressDetails.Country.AdministrativeArea.Locality.LocalityName; 
						this.user.zoom = (this.app.base_conf.zoom);
					
						this.fireEvent( 'onGrantedUserLocation', this.user );
					}else
						this.fireEvent( 'onUserLocationBadAccuracy', place.AddressDetails.Accuracy ); 
				};

				// Gah, another query to geocode this shit!
				var geocoder = new google.maps.ClientGeocoder();
				geocoder.setBaseCountryCode( ($defined(this.app) ? this.app.localization.baseCountry : 'br' ) ); 
				geocoder.getLocations( new google.maps.LatLng( this.user.lat, this.user.lng ) , fillCity.bind(this) ); 
            };

            var permissionDenied = function( error ){
                this.fireEvent('onDeniedUserLocation', error );
            };

			// Do a GeoAPI query
            location_api.addEvents( {'onDeniedUserLocation': permissionDenied.bind(this), 'onGrantedUserLocation': permissionGranted.bind(this)} );
            location_api.getGeoUserLocation();
            return;

        } else {

            // Maybe google can match your IP...
            if( typeof(google) != 'undefined' && !!google.loader.ClientLocation){
                this.user.lat   = google.loader.ClientLocation.latitude;
                this.user.lng   = google.loader.ClientLocation.longitude;
                this.user.city  = google.loader.ClientLocation.address.city; 

                this.user.zoom  = this.app.base_conf.zoom;
            }else{
                // Yeah, we don't have any idea from where you are... too bad...
                var city        = this.app.cities[ this.app.base_conf.city ];
                this.user.city  = this.app.base_conf.city; 

                this.user.lat   = city[0];
                this.user.lng   = city[1];
                this.user.zoom  = city[2];
                this.user.addr  = city[4];
            }
        }
    },

    /*
     * Validate the given address. Calls
	 * opts['onAddress'] with the status message and
	 * the GeoQuery for that address..
     */
    didYouMean: function (addr, opts){
        this.geo.getLocations(this.__genSearchString__(addr), function (result){
             var suggestions = [];

              // ===== If there was more than one result, "ask did you mean" on them all =====
              if (result.Placemark && result.Placemark.length > 2) { 
				if(!result.Placemark)
					opts['onAddress']( false, null );
				else {
					for (var i=0; i<result.Placemark.length; i++) 
						suggestions.push(result.Placemark[i].address);

					opts['onAddress']( false, suggestions );
				}
              } else {
                opts['onAddress']( true, result.Placemark );
              }
        });
    },

    run: function(){

        if(!this.options.AppAsset.ready || !this.options.StylesheetAsset.ready )
            return;

        // Load our extensions first
        this.extendGMapsApi();

        var _ns             = google.maps;
        this.map            = new _ns.Map2( $E(this.options.map_el) );
        this.geo            = new _ns.ClientGeocoder();

        // Carefull:
        // Gmaps only loads the tile after the initial setCenter. setCenter should be only LATER (in terms
        // of code poetry ), but we put it here to load faster. Deal with the boiler plate around it!
        this.map.setCenter( new _ns.LatLng( this.user.lat, this.user.lng ), this.user.zoom );

        this.regenPathConfig();
        this.control          = new RiderGControl();
        this.dir              = new _ns.Directions( null,   $E(this.options.direction_el) ); 
        this.route            = new RiderGRoute();
        this.featureManager   = new RiderFeatureManager();//    {app: this.options.AppAsset} );

        if(this.app.zoom.smooth)        this.map.enableContinuousZoom();

        if(this.app.zoom.scroll)        this.map.enableScrollWheelZoom(); 


        this.map.addControl( this.control );
 
//        if(this.app.categories.default) this.enableCategories( this.app.categories.default );
        
        // #BUG: Check issue 1620 (http://code.google.com/p/gmaps-api-issues/issues/detail?id=1620) 
        //this.dir              = new _ns.Directions( null,  null );

        this.registerDefaultEvents();

        // Ugly hack, motherfucker!
        this.ready  = true; 
        this.fireEvent( 'ready' );

        return this;
    },

    stop: function(){
        this.unregisterDefaultEvents();
        google.maps.Unload();
    },

    extendGMapsApi: function(){
        var _e_ = new RiderGExtensions();
        var _h_ = new RiderGHacks();

        _h_.hackZoom( this.app.zoom.min , this.app.zoom.max );

        return this;
    },

	hackGMapsApi: function(){
        var _h_ = new RiderGHacks();
		_h_.makeCopyrightSmaller();
	},

    loadCity: function( _city ){
        //if(!(_city in this.app.cities))
        //    return;

        // Clean old markers (hints/features) and directions
        this.cleanFeatures();
        this.route.clean( this.map );

        var city = this.app.cities[_city];
        var pos  = new google.maps.LatLng( city[0], city[1] ); 

		// Center on the correct spot
		this.map.checkResize();
        this.map.setCenter( pos, city[2] );

        // Set city and POI
		this.user.lat		 = city[0];
		this.user.lng		 = city[1];
        this.user.city       = _city;
        this.user.addr		 = city[4]; 
    },

    /*
     *  Register Default events for Google Maps
     */
    registerDefaultEvents: function(){
        if(this.ready)
            return;

        var _ns = google.maps;
        this.__ev_tooltip_open__    = _ns.Event.bind( this.map, 'extinfowindowopen',            this, this.onTooltipOpen  );
        this.__ev_tooltip_close__   = _ns.Event.bind( this.map, 'extinfowindowbeforeclose',     this, this.onTooltipClose );
        this.__ev_gdir_load__       = _ns.Event.bind( this.dir, 'load',    this, this.onNewDirections   );
        this.__ev_gdir_error__      = _ns.Event.bind( this.dir, 'error',   this, this.onErrorDirections );

        return this;
    },

    unregisterDefaultEvents: function(){
        if(!this.ready)
            return;

        var _ns = google.maps;
        _ns.Event.removeListener( this.__ev_gdir_load__ );
        _ns.Event.removeListener( this.__ev_gdir_error__ );
        _ns.Event.removeListener( this,__ev_tooltip_open__ );
        _ns.Event.removeListener( this,__ev_tooltip_close__ );

        return this;
    },

    /*
     * Given a sentence, converts them to a address format
     * expected by google maps. The current city/state/country is
     * is appended (since you PROBABLY wanna search in that area)
     */
    __genSearchString__: function( addr ){
        if($type(addr) == 'number')
            return addr;

        // Is a LatLng in format NUMBER,NUMBER ?
        else if(addr.test( /^[+-]*(\d+)([\.]*(\d+)),[+-]*(\d+)([\.]*(\d+))$/ )) 
            return addr;

        return '{street} - {city} , {country}'.substitute({
														'street':       addr,
														'city':			this.user.city,
														'country':      this.app.localization.country 
													});

    },
 
    /*
     * Given a start/end point (as addresses)
     * Get the Minimal Route Between the 2 points
     */
    getPath: function( fromAddr, toAddr ){

        this.regenPathConfig();
        this.dir.loadFromWaypoints( [this.__genSearchString__(fromAddr), this.__genSearchString__(toAddr)], this.gmaps_dir_opt );

        return this;
    },

    /*
     * Get a path from latlng A to latlng B
     */
    getRawPath: function(fromPoint, toPoint){
        this.regenPathConfig();
        this.dir.loadFromWaypoints( [fromPoint, toPoint], this.gmaps_dir_opt );

        return this;
    },

    /*
     * @see getPath
     * Same thing as getPath, but you can specify a passingBy Array.
     * Get the minimal Route between fromAddr, passingBy and them toAddr
     */
    getPathPassingBy: function( fromAddr, toAddr, passingBy ){
        this.regenPathConfig();
        this.dir.loadFromWaypoints( [this.genSearchString(fromAddr)].combine(passingBy).include( this.genSearchString(toAddr) ) , this.gmaps_dir_opt);

        return this;
    },

    /* 
     * Config Directions Options may change over time (walking, driving, etc), so we 
     * need to regenerate every query made.
     */
    regenPathConfig: function(){
        this.gmaps_dir_opt      = { 'locale':           this.app.localization.language,
                                    'travelMode':       this.app.base_conf.travelMode,
                                    'getPolyline':      true,
                                    'getSteps':         true,
                                    'avoidHighways':    false,
                                    'preserveViewport': true 
                                  };
        return this;
    },

    /*
     * Show/Hide the markers from that categories
     */
    enableCategories: function( categories ){
       var allCategories = this.featureManager.getAllCategories();

       allCategories.each(function(category_acronym,ix){
            if( category_acronym in categories ){
                this.enableCategory( category_acronym );
            }else{
                this.disableCategory( category_acronym );
            }
       }.bind(this));
    },
    enableCategory: function( category ){
		var categoryAsArr = [{
								name:	  category, 
								enabled:  true,
								data:	  null
							 }];

        this.featureManager.enableCategory( category );

		// Clean all markers
		if(this.markerClusterer){
			this.markerClusterer.clearMarkers();
			this.markerClusterer = null;
		}

		// Add the enabled markers
		var allEnabledMarkers = this.featureManager.getMarkers();

		// Cluster way of adding the features 
		if(this.app.markerClusterer.enabled){
			this.markerClusterer = new MarkerClusterer( this.map , null, { 
																			maxZoom:     this.app.markerClusterer.maxZoom , 
																			gridSize:    this.app.markerClusterer.gridSize, 
																			styles:      this.gss[this.app.markerClusterer.gss] 
																			} );
	
			var gmapsMarkers = allEnabledMarkers.map( function(marker){ return marker.marker } );

			// Attach to clusterer!
			this.markerClusterer.addMarkers( gmapsMarkers );
		}

    },
    disableCategory: function( category ){
		var categoryAsArr = [{
								name: category, 
								enabled: true,
								data: null
							 }];

        this.featureManager.disableCategory( category );

        //Remove the HintTooltip
        var categoryClass = "box" + category;
        if ($E("#HintTooltip").className.toLowerCase() == categoryClass) {
	            var gmap = $E('#gmaps-holder').retrieve('map_api');
				gmap.map.closeExtInfoWindow();
        }

		// Clean all markers
		if(this.markerClusterer){
			this.markerClusterer.clearMarkers();
			this.markerClusterer = null;
		}

		// Add the enabled markers
		var allEnabledMarkers = this.featureManager.getMarkers();

		// Cluster way of adding the features 
		if(this.app.markerClusterer.enabled){
			this.markerClusterer = new MarkerClusterer( this.map , null, { 
																			maxZoom:     this.app.markerClusterer.maxZoom , 
																			gridSize:    this.app.markerClusterer.gridSize, 
																			styles:      this.gss[this.app.markerClusterer.gss] 
																			} );
			var gmapsMarkers = allEnabledMarkers.map( function(marker){ return marker.marker } );
	
			// Attach to clusterer!
			this.markerClusterer.addMarkers( gmapsMarkers );
		}
    },
   
    /*
     * Clean old features
     */
    cleanFeatures:  function(){

        // Remove references on markerClusterer first!
        if(this.markerClusterer){
            this.markerClusterer.clearMarkers();
            this.markerClusterer = null;
        } 

        if(this.featureManager)
            this.featureManager.cleanAllCategories();
    },

	/*
 	 * Show the features nearby you
  	 * indicating the radius and your position
     */
	showFeaturesNearby: function ( pos , meters){

		var _meters = meters || parseInt(this.gss.userLocationMarker.accuracy.meters);

		this.route.clean(this.map)

		// Single Marker
		this.route.whereami.pos    = pos;
		this.route.whereami.marker = new google.maps.Marker( pos , this.gss.userLocationMarker );
		this.map.addOverlay( this.route.whereami.marker );

		// The circle around you 
		
		this.route.whereami.overlay = pos.circlePoly( _meters/1000, 40 , this.gss.userLocationMarker.accuracy.lineColor, 
															this.gss.userLocationMarker.accuracy.lineWidth,
															this.gss.userLocationMarker.accuracy.lineOpacity,
															this.gss.userLocationMarker.accuracy.fillColor,
															this.gss.userLocationMarker.accuracy.fillOpacity
													);
		/* Andre asked to remove this 'Pedro */
		/* this.map.addOverlay( this.route.whereami.overlay ); */

		this.getCoolFeaturesOnCircle( this.route.whereami.overlay );
		this.map.panTo( pos ); 

        this.fireEvent( 'onFeaturesNearby' , {'meters': meters, 'pos': pos} );

		// Fire dragging Single Marker event
        GEvent.bind( this.route.whereami.marker,     'dragend',   this, function(point){
			this.fireEvent('onSingleMarkerRelease', point);	
		}); 
	},


    /*
     * Get the features nearby the circle. Will only
 	 * display the tips.
     */
    getCoolFeaturesOnCircle:  function( circlePoly ){

        var onNewFeatures = function( featureList ){
            this.cleanFeatures();

			var gmarkerPerma = null;
            /*
             * Create and register the features!
             * NOTE: Even if 30 markers on the screen are clustered, we have already created them!
             * This means we have a huge overhead on creating/deleting markers, altought the 
             * performance is quite good on zooming and so on.
             */
            featureList.each( function( feature,index ){
                
                var typed_category          = feature.category.split(' ')[0];
                var valid_category          = null;

                this.app.categories.info.each( function( category ){
                    if(category.acronym == typed_category) 
                        valid_category = category; 
                });
                if(!valid_category)
                    return;

                var marker = this.createNewMarker( feature, valid_category, this.gss[valid_category.gss] );

                if(!this.app.markerClusterer.enabled)
                    this.map.addOverlay( marker );

                // Save the marker \o/                        
                this.featureManager.attachMarker( valid_category, marker, feature );

				/*
				 * Save some information for permalink
				 */
				if(feature.id == current_hint)
					gmarkerPerma = marker;

            }.bind(this));

            // Cluster way of adding the features 
            if(this.app.markerClusterer.enabled){
                this.markerClusterer = new MarkerClusterer( this.map , null, { 
                                                                               maxZoom:     this.app.markerClusterer.maxZoom , 
                                                                               gridSize:    this.app.markerClusterer.gridSize, 
                                                                               styles:      this.gss[this.app.markerClusterer.gss] 
                                                                             } );
                                                                             
        
                // Attach to clusterer!
                var allMarkers = this.featureManager.getMarkers().map( function(marker){ return marker.marker } );
                this.markerClusterer.addMarkers( allMarkers );
            }

			/*
			 * Display the hint, if the permalink say's so!
			 */
			if(current_hint){
				var marker = this.featureManager.getMarker( gmarkerPerma );
				if(!marker)
					return this;

				var latlng                 = new google.maps.LatLng( marker.info.gmaps_loc.coords[0].y , marker.info.gmaps_loc.coords[0].x );
				this.map.setCenter( latlng, 17 );

				this.fireEvent('featureClick', {'latlng': latlng, 'marker': marker.marker , 'category': marker.marker.valid_category} ); 
				current_hint = null;
			}

			// Fire Event
        };

        var geoJsonRequest = new RiderGeoJson( {'onNewFeatures': onNewFeatures.bind(this)} );
        geoJsonRequest.getFeaturesOnPoly(  circlePoly, this.featureManager.getEnabledCategories() );

        return this;
    },

    /*
     * Retrieves features inside a boundingBox
     */
    getCoolFeaturesOnBB: function( bb ){

        var onNewFeatures = function( featureList ){
            this.cleanFeatures();

			var gmarkerPerma = null;
            /*
             * Create and register the features!
             * NOTE: Even if 30 markers on the screen are clustered, we have already created them!
             * This means we have a huge overhead on creating/deleting markers, altought the 
             * performance is quite good on zooming and so on.
             */
            featureList.each( function( feature,index ){
                
                var typed_category          = feature.category.split(' ')[0];
                var valid_category          = null;

                this.app.categories.info.each( function( category ){
                    if(category.acronym == typed_category) 
                        valid_category = category; 
                });

                if(!valid_category)
                    return;

                var marker = this.createNewMarker( feature, valid_category, this.gss[valid_category.gss] );

                if(!this.app.markerClusterer.enabled)
                    this.map.addOverlay( marker );

                // Save the marker \o/                        
                this.featureManager.attachMarker( valid_category, marker, feature );

				/*
				 * Save some information for permalink
				 */
				if(feature.id == current_hint)
					gmarkerPerma = marker;

            }.bind(this));

            // Cluster way of adding the features 
            if(this.app.markerClusterer.enabled){
                this.markerClusterer = new MarkerClusterer( this.map , null, { 
                                                                               maxZoom:     this.app.markerClusterer.maxZoom , 
                                                                               gridSize:    this.app.markerClusterer.gridSize, 
                                                                               styles:      this.gss[this.app.markerClusterer.gss] 
                                                                             } );
        
                // Attach to clusterer!
                var allMarkers = this.featureManager.getMarkers().map( function(marker){ return marker.marker } );
                this.markerClusterer.addMarkers( allMarkers );
            }

			/*
			 * Display the hint, if the permalink say's so!
			 */
			if(current_hint){
				var marker = this.featureManager.getMarker( gmarkerPerma );
				if(!marker)
					return this;

				var latlng                 = new google.maps.LatLng( marker.info.gmaps_loc.coords[0].y , marker.info.gmaps_loc.coords[0].x );
				this.map.setCenter( latlng, 17 );

				this.fireEvent('featureClick', {'latlng': latlng, 'marker': marker.marker , 'category': marker.marker.valid_category} ); 
				current_hint = null;
			}
        };

        var geoJsonRequest = new RiderGeoJson( {'onNewFeatures': onNewFeatures.bind(this)} );
        geoJsonRequest.getFeaturesOnBB(  bb , this.featureManager.getEnabledCategories() );

        return this;
    },

    getCoolFeaturesNearbyRoute: function( poly , radius ){

        var onNewFeatures = function( featureList ){
            this.cleanFeatures();

			var gmarkerPerma = null;
            /*
             * Create and register the features!
             * NOTE: Even if 30 markers on the screen are clustered, we have already created them!
             * This means we have a huge overhead on creating/deleting markers, altought the 
             * performance is quite good on zooming and so on.
             */
            featureList.each( function( feature,index ){
                
                var typed_category          = feature.category.split(' ')[0];
                var valid_category          = null;

                this.app.categories.info.each( function( category ){
                    if(category.acronym == typed_category) 
                        valid_category = category; 
                });

                if(!valid_category)
                    return;

                var marker = this.createNewMarker( feature, valid_category, this.gss[valid_category.gss] );

                if(!this.app.markerClusterer.enabled)
                    this.map.addOverlay( marker );

                // Save the marker \o/                        
                this.featureManager.attachMarker( valid_category, marker, feature );

				/*
				 * Save some information for permalink
				 */
				if(feature.id == current_hint)
					gmarkerPerma = marker;

            }.bind(this));

            // Cluster way of adding the features 
            if(this.app.markerClusterer.enabled) {
                this.markerClusterer = new MarkerClusterer( this.map , null, { 
                                                                               maxZoom:     this.app.markerClusterer.maxZoom , 
                                                                               gridSize:    this.app.markerClusterer.gridSize, 
                                                                               styles:      this.gss[this.app.markerClusterer.gss] 
                                                                             } );

                // Attach to clusterer!
                var allMarkers = this.featureManager.getMarkers().map( function(marker){ return marker.marker } );
                this.markerClusterer.addMarkers( allMarkers );
            }

			/*
			 * Display the hint, if the permalink say's so!
			 */
			if(current_hint){
				var marker = this.featureManager.getMarker( gmarkerPerma );
				if(!marker)
					return this;

				var latlng                 = new google.maps.LatLng( marker.info.gmaps_loc.coords[0].y , marker.info.gmaps_loc.coords[0].x );
				this.map.setCenter( latlng, 17 );

				this.fireEvent('featureClick', {'latlng': latlng, 'marker': marker.marker , 'category': marker.marker.valid_category} ); 
				current_hint = null;
			}
        };

        var geoJsonRequest = new RiderGeoJson( {'onNewFeatures': onNewFeatures.bind(this)} );
        geoJsonRequest.getFeaturesOnRoute( poly, radius, this.featureManager.getEnabledCategories());
    },

    // Bounding Box                            
    bbOverlayHide: function(){
        if(this.__bb_polygon__)
            this.map.removeOverlay( this.__bb_polygon__ );
    },
    bbOverlayShow: function(){
        if(this.__bb_polygon__)
            this.map.addOverlay( this.__bb_polygon__ )
    },

    createNewMarker: function( feature , category, gss){
        var latlng                 = new google.maps.LatLng( feature.gmaps_loc.coords[0].y , feature.gmaps_loc.coords[0].x );
        var marker                 = new google.maps.Marker( latlng, gss ); 

		/*
		 * Do a hover
		 */
		marker.state          = 'mouseout';
		marker.gss            = gss;
		marker.valid_category = category;
		GEvent.addListener( marker, 'mouseover', function(){
			if(marker.state != 'mouseout')
				return;

			marker.setImage(gss.icon_hover_src);
			marker.state = 'mouseover';
		});

		GEvent.addListener( marker, 'mouseout', function(){
			if(marker.state != 'mouseover')
				return;

			marker.setImage(gss.icon_src);
			marker.state = 'mouseout';
		});
        google.maps.Event.bind( marker, 'click', {'gmap': this, 'marker': marker,'category': category}, this.onMarkerClick );

        return marker;
    },

    addMarker: function( valid_category, marker , feature ){
        this.map.addOverlay( marker );

        // Save the marker \o/                        
        this.featureManager.attachMarker( valid_category, marker, feature );
    },

/*********************************************************************************
 *                          Event Callbacks                                      *
 *********************************************************************************/

    /* Fixed a THIS leaking, don't annoy me around the SELF variable */
    onMarkerClick: function( latlng ){
        var self = this.gmap;
        self.fireEvent('featureClick', {'latlng': latlng, 'marker': this.marker , 'category': this.category} ); 
    },


    onTooltipOpen: function(){
        this.fireEvent('onTooltipOpen');
    },
    onTooltipClose: function(){
        this.fireEvent('onTooltipClose');
    },
 


    /*
     * The point A and B marker drag/drop 
     */
    onStartMarkerDrag: function( point ){
        this.fireEvent( 'onStartMarkerDrag', point );
    },
    onStartMarkerRelease: function( point ){
        var fromAddr            = point;  
        var toAddr              = this.route.end.pos;

        this.fireEvent( 'onStartMarkerRelease', point );
        this.fireEvent( 'onSEMarkerRelease' , { 'start': point.toUrlValue(), 'end': this.route.end.pos.toUrlValue() } );
    },

    onEndMarkerDrag: function( point ){
        this.fireEvent( 'onStartMarkerDrag', point ); 
    },

    onEndMarkerRelease: function( point ){
        var toAddr            = point;  
        var fromAddr          = this.route.start.pos; 

        this.fireEvent( 'onEndMarkerRelease', point );
        this.fireEvent( 'onSEMarkerRelease' , { 'start': fromAddr.toUrlValue(), 'end': toAddr.toUrlValue() } );
    },

    /*
     *  Default GoogleMaps callbacks
     */
    onNewDirections: function(){

        // Clean old things (like events!) 
        this.route.clean( this.map );

        // Why recreate everything? Because google maps is a fucker, it only
        // has constructors, but no public variables (at least not with a SANE name) and
        // getters&setters are forbidden. So, gmaps objects are MAINLY write-once, read-many
        this.route.update( this.dir, this.dir.getRoute(0) );
        this.route.addToMap( this.map );
        this.route.addEvents({ 
                                        'onStartMarkerDrag':        this.onStartMarkerDrag.bind(this),
                                        'onStartMarkerRelease':     this.onStartMarkerRelease.bind(this),
                                        'onEndMarkerDrag':          this.onEndMarkerDrag.bind(this),
                                        'onEndMarkerRelease':       this.onEndMarkerRelease.bind(this)
                                     });
        this.route.registerPolyEditingEvents();

        /*
         * Events
         */
        var bb = this.route.start.pos.bb( this.route.end.pos );

        //this.route.polyline;
        this.fireEvent( 'onNewPath' , {'bb':bb, 'poly': this.route.polyline} );
    },

    onErrorDirections: function(){
       var error_code = this.dir.getStatus().code;

       if( error_code in this.status_code )
           this.fireEvent( 'onNewPathError', {'code': error_code, 'description': this.status_code[error_code]} );
        else
            console.error('GoogleMaps returned a Direction(path) with a error status i cant fucking understand: ' + error_code );
    }
});




















































/*
 * Fetch and mantain references to the assets.
 * Singleton.
 */
(function(){
    var __instance__ = null;

    this.RiderAssets = new Class({
        __version__:  '0.0.1',
        __author__:   'Bob esponja calca quadrada',
        __doc__:      'Fetch and mantain references to the assets as a singleton(global)',

        initialize: function( ){
            if(__instance__)
                return __instance__;

            __instance__ = this;                     
        },

        /*
         * Receives a AssetsRequest, an object 
         * with this propertys {'name': string, 'uri': string, onSuccess: function} 
         */
        load: function( asset_request ){
            var asset = asset_request ;

            if(this.get( asset ) && $defined(asset.onSuccess)){
                asset.onSuccess( this.get(asset) );
                return;
            }

            new Request.JSON( {url: asset.uri, method: 'GET',onSuccess: function( _nil_, txt){
                this[asset.name] = JSON.decode(txt);
                if($defined(asset.onSuccess))
                    asset.onSuccess( this[asset.name] );
            }.bind(this)}).get();
        },

        get: function( name ){

            if(! $defined( this[name] ))
                return null;

            return this[name];
        }
    });
})();


































































/*
 * Gringo GoogleMaps Route with Multiple Waypoints and dragndrop editing.
 *
 * Vitor Alves Calejuri <ondeosfrangosnaotemvez@gmail.com>
 *
 * Features:
 *  Draggable, Customizable and Easier GUI Editing
 *
 *  Note:
 *   - Duo the self-recursing nature of routes with subroutes(waypoints),
 *    this structure is self-referencing and recursive.
 *
 *   - The first level of the route have start_marker and end_marker,
 *     All other childs have only waypoint_marker (if needed). #hardcoded 
 *
 *   - To get full direction/steps/duration , you must traversal
 *     all the tree! Check function foreachChild().
 *
 */
var RiderGRoute = new Class({

   __version__: '0.0.7',
   __author__:  'Bob esponja calca quadrada',
   __doc__:     'User editable Route, support waypoints & dragndrop ',

   Implements:  [Options, Events, Chain ],

   /*
    * Public methods 
    */
   ready:               false,
   dir:                 null,
   route:               null,
   steps:               [],         
   duration:            '',
   distance:            '',

   polyline:            null,        // google.maps.Polyline Object
   searcharea:          null,        // A polyline object indicating the search area

   start:               { 'marker': null, 'pos': null , 'addr': null},
   end:                 { 'marker': null, 'pos': null , 'addr': null},
   whereami:            { 'marker': null, 'overlay': null, 'pos': null},
   waypoint_marker:     null,        // Waypoint marker (null at the beggining)

   options: { 
        radius:                 1.0  // Radius of the "extrude" operation (in km!)
   },

    initialize: function( _options ){
        this.setOptions( _options );

        this.gss  = new RiderAssets().get('gss');
    },

    clean: function(map){

        if(this.whereami.marker){
            map.removeOverlay(  this.whereami.marker ); 
            this.whereami.marker = null;
			this.whereami.pos = null;
        }

        // Unregister events and remove from map
        //if(!this.ready)
        //    return;

        this.unregisterPolyEditingEvents();
        this.rmFromMap( map );

    },

    update: function( dir, groute ){
        var _ns         = google.maps;

        // Save all steps in LatLng format
        this.dir        = dir;
        this.route      = this.dir.getRoute(0);

        // StartMarker and endMarker are ALREADY in the correc tposition
        // because the user is... DRAGGING THE START or END MARKER!
        this.start.pos    = this.route.getStep(0).getLatLng();
        this.start.addr   = this.route.getStartGeocode().address;
        this.end.pos      = this.route.getEndLatLng();
        this.end.addr     = this.route.getEndGeocode().address;

        if(!this.start.marker)
            this.start.marker = new _ns.Marker( this.route.getStep(0).getLatLng(),         this.gss.startMarker );
        if(!this.end.marker)
            this.end.marker   = new _ns.Marker( this.route.getEndLatLng(),                 this.gss.endMarker );

        this.start.marker.setLatLng( this.start.pos ); 
        this.end.marker.setLatLng( this.end.pos ); 

        var end         = groute.getEndLatLng();
        var stepLen     = groute.getNumSteps();

        this.steps.empty();
        for( var i=0; i < stepLen; ++i)
            this.steps.include( groute.getStep(i).getLatLng() );
        this.steps.include( end );

        // Create polyline
        this.polyline        = dir.getPolyline(); 
        this.polyline.setStrokeStyle( this.gss.line );

        this.ready = true;
    },

    registerPolyEditingEvents: function(){
        var _ns = google.maps;

        this.__ev__polyover__          = _ns.Event.bind( this.polyline,         'mouseover', this, this.onPolyMouseOver );  
        this.__ev__smarker_drag__      = _ns.Event.bind( this.start.marker,     'drag',      this, this.onSMarkerDrag );
        this.__ev__emarker_drag__      = _ns.Event.bind( this.end.marker,       'drag',      this, this.onEMarkerDrag );

        this.__ev__smarker_rls__       = _ns.Event.bind( this.start.marker,     'dragend',   this, this.onSMarkerRelease );
        this.__ev__emarker_rls__       = _ns.Event.bind( this.end.marker,       'dragend',   this, this.onEMarkerRelease );
    },

    unregisterPolyEditingEvents: function(){
        var _ns = google.maps;

        if(this.__ev__polyover__)
            _ns.Event.removeListener( this.__ev__polyover__ );
        if(this.__ev__smarker_drag__)
            _ns.Event.removeListener( this.__ev__smarker_drag__ );
        if(this.__ev__emarker_drag__)
            _ns.Event.removeListener( this.__ev__emarker_drag__ );

        if(this.__ev__smarker_rls__)
            _ns.Event.removeListener( this.__ev__smarker_rls__ );
        if(this.__ev__emarker_rls__)
            _ns.Event.removeListener( this.__ev__emarker_rls__ );
    },

    /* 
     * Add Markers/Polyline to the map 
     */
    addToMap: function( map ){

        // Polyline
        map.addOverlay( this.polyline );

        // Markers & Waypoints
        map.addOverlay( this.start.marker );
        map.addOverlay( this.end.marker );

        if($defined(this.waypoint_marker))
            map.addOverlay( this.waypoint_marker );
    },

    rmFromMap: function( map ) {

        if(this.polyline)
            map.removeOverlay(  this.polyline ); 
        if(this.whereami.overlay)
            map.removeOverlay(  this.whereami.overlay ); 

        if(this.whereami.marker)
            map.removeOverlay( this.whereami.marker );
        if(this.start.marker)
            map.removeOverlay( this.start.marker );
        if(this.end.marker)
            map.removeOverlay( this.end.marker );

        if($defined(this.waypoint_marker))
            map.removeOverlay( this.waypoint_marker );
    },

    
    /*
     * Given a radius in km, get the qtd of pixels to 
     * correctly display the distance in determined zoom level
     */
    radiusToPx: function( map , _zoom){

        var proj                = map.getCurrentMapType().getProjection();
        var _ns                 = google.maps;

        // 1degree =~ 111km, then,  1km =~ 0.0090090090090090089 deegres
        var p1                  = map.getCenter();
        var p2                  = new _ns.LatLng( p1.lat() + (0.0090090090090090089*this.options.radius), p1.lng() ); 
        var zoom                = ($defined(_zoom) ? _zoom : map.getZoom() );
        
        var p1px                = proj.fromLatLngToPixel( p1 , zoom );
        var p2px                = proj.fromLatLngToPixel( p2 , zoom );

        return Math.abs( p2px.y - p1px.y );
    },

	/*
	 * Given the route, find the most appropriate
	 * zoom level and center point. 
	 */
	getAppropriateZoomLevel: function( map ){
		if(!this.steps) 
			return -1;


		var bounds = new GLatLngBounds();
		for (var i=0; i< this.steps.length; i++) 
			bounds.extend(this.steps[i]);

		return { 'zoom': map.getBoundsZoomLevel( bounds ),
				 'center': bounds.getCenter() };
	},
    
    /*
     * Given a route, get a searcharea
     * polyline of the given radius
     */
    getPolygonSearchArea: function( map ){

        var pxWidth             = this.radiusToPx( map );
        this.searcharea         = new google.maps.Polyline( this.steps , this.gss.searcharea.color, pxWidth, this.gss.searcharea.opacity );

        return this;
    },

    /**
     * Given a route, get the searcharea
     * points of the enclosing polygon
     */
    getSearchArea: function( map ){
        var offset              = 100; // this.radiusToPx(map); 
        var zoom                = 18;
        var proj                = map.getCurrentMapType().getProjection();

        return this.polyline.getEnclosingPolygon( map, zoom, offset ); 
    },

    /*
     * Simplify the polyline using douglas-peucker algorithm.
     * Aggr controls the aggressivity used in
     * algorithm
     */
    simplifySteps: function( map ){
        var stepAsPoints = new Array();
        var proj         = map.getCurrentMapType().getProjection();
        var zoom         = map.getZoom(); 

		// NOTE: This probably will increase performance, but right now, this
		// is not a problem. 
        this.steps.each( function( step, ix){
            stepAsPoints.include( proj.fromLatLngToPixel( step, zoom ) );
        });

        return stepAsPoints;
    },

    /*
     * Events
     */
    onPolyMouseOver: function(){
        return;       
    },


    onSMarkerDrag: function( latlng ){
        this.fireEvent('onStartMarkerDrag', latlng);
    },
    onEMarkerDrag: function( latlng ){
        this.fireEvent('onEndMarkerDrag', latlng );
    },
    onSMarkerRelease: function( latlng ){
        this.fireEvent('onStartMarkerRelease', latlng );
    },
    onEMarkerRelease: function( latlng ){
        this.fireEvent( 'onEndMarkerRelease', latlng );
    },

    onWMarkerDrag: function(){ return; }
});






























































/*
 * Group the markers in category, and also
 * hold every info of the marker (info property)
 *
 * Ps: Take care with markerClusterer
 */
var RiderFeatureManager = new Class({
    Implements: [ Events, Options, Chain ],

    /*
     * markers: {
     *              'categoryOne': { enabled: true, name: 'categoryOne', data: [{
     *                                                        marker:  google.maps.Marker,
     *                                                        info:    jsonInfo,
     *                                                     },{ marker2 } , ... , { markerN } ] 
     *                             },
     *              'categoryTwo': { enabled: true, data: [{
     *                                                          ...
     *                                                      }, ... ]
     *                             },
     *                 ...
     *              'categoryN':   ... 
     *          }
     */
    markers:            $H(),
	currentClickedMarker: null,

    options: {
    },

    initialize: function( _opts ) {
        var asset   = new RiderAssets(); 
        this.gss    = asset.get( 'gss' );
        this.app    = asset.get( 'app' );

        /*
         * Create the basic structure
         */
        this.app.categories.info.each( function(acc,ix){
            this.newCategory( acc.acronym );
        }.bind(this));

    },

    /*
     * Clean/Create categories!
     */
    newCategories: function( categories ){
        categories.each( function( category, ix ){
            this.newCategory( category );
        }.bind(this));
    },
    cleanAllCategories: function(){
        this.cleanCategories( this.getAllCategories() );
    },
    cleanCategories: function( categories ){
        categories.each( function( category, ix ){
            this.cleanCategory( category );
        }.bind(this));
    },

    newCategory:      function( category ){   this.__setCategory__( category, true);         },
    cleanCategory:    function( category ){   this.__setCategory__( category, false );       },
    categoryExists:   function( category ){   return (this.markers.get( category ) != null && this.markers.get(category).length != 0); },


    // Enable/disable categories (but does not clean!)
    enableCategory:       function(cat){      this.markers.get(cat).enabled = true;     },
    disableCategory:      function(cat){      this.markers.get(cat).enabled = false;    },

    getAllCategories: function(){             return this.markers.getKeys(); },
    getDisabledCategories: function(){        return this.markers.filter( function(cat){ return !cat.enabled; }); },
    getEnabledCategories: function(){         return this.markers.filter( function(cat){ return cat.enabled;  }); },
    isCategoryEnabled:  function( cat ){
        return this.markers.get(cat).enabled;                           
    },


    /*
     * Get a single marker by GMapsMarker type
     * TODO: Improve performance, searching all the tree is slow...
     */
    getMarker:      function( gmaps_marker ){
        var mark = null;
        this.getEnabledCategories().each( function(cat){
            var marker = this.getMarkerByCategory( cat.name, gmaps_marker );
            if(marker)
                mark = marker;
        }.bind(this));

        return mark;
    },
    getMarkerByCategory:      function( cat, gmaps_marker ){
        var mark = null;
        this.markers.get(cat).data.each( function(marker, i){
            if(marker.marker == gmaps_marker)
                mark = marker;
        }.bind(this));

        return mark;
    },

    /*
     * Get a LOT of markers.
     *
     * First argument is optional, its an array of categories.
     * Markers will be filtered by that categories (only return markers that
     * belong to one of categories)
     */
    getMarkers:      function( ){
       var categories = (arguments.length ? arguments[0] : this.getEnabledCategories() );

       var allMarkers = [];
       categories.each( function(cat, ix){
            this.markers.get(cat.name).data.each( function( marker, i ){
                allMarkers.include( marker );
            }.bind(this));
       }.bind(this));

       return allMarkers;
    },

    /*
     * Attach and detach markers
     */
    attachMarker: function( category, gmaps_marker, json_info ){
        if(!this.categoryExists(category.acronym))
            this.newCategory( category.acronym );

        var cat = this.markers.get( category.acronym );
        var obj = new Hash({marker: gmaps_marker, info: json_info} );

        cat.data.include( obj ); 

        return obj;
    },

    /*
     * Beware, you can leak memory if you not close marker/
     * info before detaching!
     */
    dettachMarker: function( category, gmaps_marker ){
        this.markers.get(category).data.erase( gmaps_marker );
    },

    /*
     * Destroy marker (Careful...)
     */
    destroyMarker: function( category, gmaps_marker ){
        this.markers.get(category).data.each( function(marker){
            if(marker.marker == gmaps_marker){
                marker.marker   = null;
                marker.info     = null;
            }
        }.bind(this));
    },


    /*
     * Private
     */
    __setCategory__: function( cat, del_or_inc/* True = include, False = delete */){

        if(this.categoryExists(cat) == del_or_inc )
            return;

        if(del_or_inc){ 
            this.markers.include( cat, $H({'enabled': true, 'name': cat, 'data': [] }) );
        } else { 
            this.markers.get( cat ).data.empty();
            this.markers.get( cat ).enabled = true;
        }
    }
});

/*
 * Simple abstraction to send/receive
 * data from our servers. 
 *
 * Protocol used is GeoJson
 */

var RiderGeoJson = new Class({
    __version__:        '0.0.1',
    __author__:         'Bob esponja calca quadrada',
    __doc__:            'GeoJson Helper',

    Implements:         [Options, Events, Chain],

    initialize: function( _option ){
        this.setOptions( _option );
        return this;
    },

    /*
     * Send current Route in GeoJson
     * format (lineString).
     *
     * @input opts {url, data(LatLngArray), onSuccess }
     */
    sendRoute:   function( opts ){
       var geoJson = {
                       'type':          'LineString',
                       'coordinates':   [],
                       'categories':    []
                     };
      
       var bb = opts.data.bb;
       bb.data.each(function(pos,ix){
            geoJson.coordinates.include( [pos.lat() , pos.lng()] );
       });
     
       new Request.JSON( {url: opts.url, onSuccess: opts.onSuccess} ).send( {data: geoJson} );
    },


    /*
     * Retrieve features on the given boundingBox
     * Returns a geoJson
     */
    getFeaturesOnBB:    function( bb , categories ){
        var request = new Request.JSON( {url: '/api/hints/neighboursBB.json', content_type: 'application/json', onSuccess: function(_nil_,txt){
            var featuresList = JSON.decode( txt ); 
            this.fireEvent('onNewFeatures', [featuresList] );
        }.bind(this) } ); 

        /*
         * TODO: I will avoid work on making a on-demand feature listing,
         * because in the vast majority of the cases, searching, receiving and displaying
         * the features are not a bootleneck. Maybe(unlikely) we will need this, when users
         * fill up our DB with features.
         */
        request.send( {'bb': bb} ); //,'categories': categories} );
    },

    /*
     * Retrieve features on the given Poly 
     * Returns a geoJson
     */
    getFeaturesOnPoly:    function( poly , categories ){
        var request = new Request.JSON( {url: '/api/hints/neighbours.json', content_type: 'application/json', onSuccess: function(_nil_,txt){
            var featuresList = JSON.decode( txt ); 
            this.fireEvent('onNewFeatures', [featuresList] );
        }.bind(this) } ); 

        request.send( {'polyJson': poly.toJson()} ); //,'categories': categories} );
    },

    /*
     * Retrieve features on nearby route
     * returns a geoJson
     */
    getFeaturesOnRoute: function( line, radius, categories ){
        var request = new Request.JSON( {url: '/api/hints/neighboursLine.json', content_type: 'application/json', onSuccess: function(_nil_,txt){
            var featuresList = JSON.decode( txt ); 
            this.fireEvent('onNewFeatures', [featuresList] );
        }.bind(this) } ); 

        request.send( {'lineJson': line.toJson(),'radius': radius} ); //,'categories': categories} );
    },

    getFeatureDetail: function( _id ){
        var request = new Request.JSON( {method: 'get', url: '/api/hints/detail/{id}.json'.substitute({id: _id}), content_type: 'application/json', onSuccess: function(_nil,txt){
            var featureDetail = JSON.decode( txt );
            this.fireEvent( 'onFeatureDetail', [featureDetail]);
        }.bind(this)});

        request.get();
    },

    rate: function(_id, _val){
        var request = new Request.JSON( {method: 'get', url: '/api/hints/rate/{id}/{val}.json'.substitute({id: _id,val:_val}), content_type: 'application/json', onSuccess: function(_nil,txt){
            var newHint = JSON.decode( txt ); 
            this.fireEvent('onRateSuccess', [newHint] );
        }.bind(this) } ); 

        request.get();
    },

	getHintOfTheDay: function(_city){
        var request = new Request.JSON( {method: 'get', url: '/api/hints/highlight/{city}.json'.substitute({city: _city}), content_type: 'application/json', onSuccess: function(_nil,txt){
            var newHint = JSON.decode( txt ); 
            this.fireEvent('onNewHintOfTheDay', [newHint] );
        }.bind(this) } ); 

        request.get();
	}
});

/*
 * Implements a Localization class for the newer browsers and Cellphones.
 *
 * GeoLocate        W3C Api: IPhone Safari, FF 3.5, Chrome 
 * Gears:           All browsers supported by Google gears and with gears installed.
 */
var RiderGeoLocate = new Class({
        __version__:  '0.0.1',
        __author__:   'Bob esponja calca quadrada',
        __doc__:      'Tries to locate City/Lat/Lng of the user',

        Implements: [Options,Events],

        // Gear
        gear_factory:   null,

        initialize: function( _opt ){
            this.setOptions( _opt ); 
        },

        /*
         * Init gears (if gears exists!)
         */
        initGears: function(){

            // We are already defined. Hooray!
            if(window.google && google.gears)
                return;

            if(typeof GearsFactory != 'undefined')
                this.gear_factory = new GearsFactory();
            else{
                // IE
                try {
                    this.gear_factory = new ActiveXObject('Gears.Factory');
                  // privateSetGlobalObject is only required and supported on IE Mobile on
                  // WinCE.
                  if (this.gear_factory.getBuildInfo().indexOf('ie_mobile') != -1) {
                    this.gear_factory.privateSetGlobalObject(this);
                  }
                } catch (e) {
                    // Safari
                    if($defined(navigator.mimeTypes)
                       && navigator.mimeTypes["application/x-googlegears"]) {
                        this.gear_factory = document.createElement("object");
                        this.gear_factory.style.display = "none";
                        this.gear_factory.width = 0;
                        this.gear_factory.height = 0;
                        this.gear_factory.type = "application/x-googlegears";
                        document.documentElement.appendChild(this.gear_factory);
                    }
                }
            }

          // *Do not* define any objects if Gears is not installed. This mimics the
          // behavior of Gears defining the objects in the future.
          if (!this.gear_factory) 
            return;

          // Now set up the objects, being careful not to overwrite anything.
          //
          // Note: In Internet Explorer for Windows Mobile, you can't add properties to
          // the window object. However, global objects are automatically added as
          // properties of the window object in all browsers.
          if (!window.google) 
            google = {};
          
          if (!google.gears) 
            google.gears = {factory: this.gear_factory};

        },

        /*
         * Query browser to see if it supports w3c geolocalization API.
         * It really doensn't mind if its w3c or gears. This is the most accurate
         * method.
         */
        supportGeoApi: function(){
            if($defined(navigator.geolocation) || ($defined(google) && $defined(google.gears)))
                return true;

            return false;
        },
        
        getGeoApiObject: function(){

            if(!this.supportGeoApi())
                return null;

            if($defined(navigator.geolocation)) return navigator.geolocation;    
            else                                return google.gears.factory.create('beta.geolocation');
        },

        getGeoUserLocation: function(){
            //this.initGears(); 
         
            var geolocation = this.getGeoApiObject();

            var permissionGranted = function( pos ){
                this.fireEvent( 'onGrantedUserLocation', pos );
            };
            var permissionDenied = function( error ){
                this.fireEvent( 'onDeniedUserLocation', error ); 
            };

            geolocation.getCurrentPosition( permissionGranted.bind(this), permissionDenied.bind(this) ); 
         }
});

/*
 *  Script: GringoTios.js
 *
 *  s/Tios/Utils/g
 *  
 *  DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
 *                   Version 2, December 2004
 *
 * Copyright (C) 2004 Sam Hocevar
 * 14 rue de Plaisance, 75014 Paris, France
 * Everyone is permitted to copy and distribute verbatim or modified
 * copies of this license document, and changing it is allowed as long
 * as the name is changed.
 *
 *           DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
 *  TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
 *
 *  0. You just DO WHAT THE FUCK YOU WANT TO.
 *
 */

var GringoTios = {
    'version':  '0.0.1'
}

// Array Remove - By John Resig (MIT Licensed)
Array.prototype.remove = function(from, to) {
	var rest = this.slice((to || from) + 1 || this.length);
	this.length = from < 0 ? this.length + from : from;
	return this.push.apply(this, rest);
}

Array.prototype.isEmpty = function(){
    return (this.length == 0);
}

// $E use JQuery like CSS Selectors for mootools (to avoid conflict...)
var $E = function (){
    var ret = $$(arguments);

    if(ret.length >= 0)
       return ret[0];

    return undefined;
}

/*
 * Deep linking
 */
current_perma = '';
current_mini  = '';
current_hint  = null;
current_hintMarker = null;

var $setDeepUri = function( from, to , hint , city){

//    trace("generating mini url", from, to, hint, city);

	var permalink = "";

	/*
	 * Only show the HINT, but not a route
	 */
	if(from)
		permalink += "de:" + (from.encode().toString());
	if(to)
		permalink += (from ? "&" : "") + "para:" + (to.encode().toString());
	if(hint)
		permalink += ((from || to) ? "&" : "") + "hint:" + (hint.toString(32));
	if(city)
		permalink += ((from || to || hint) ? "&" : "") + "c:" + (city);

    location.hash = permalink;
    current_perma = permalink;

    getMiniuri( location.href.split('http://')[1] , function(miniuri){
		current_mini = (miniuri['error'] ? miniuri['full'] : miniuri['short']);
			 /*
			  * Set sharing buttons
			  */
			  updateShareLink('twitter');
			  updateShareLink('facebook');

        $( '#permalink-route input' ).val( current_mini );                                                                             
    });

    return permalink;
}

var $getDeepUri = function(){
    var uri = location.hash.replace('#','');
    if(!uri)
		return null;

	var cFrom   = fromLatLng = null;
	var cTo     = toLatLng   = null;
	var cHintId = hintId     = null;
	var city = null;

    try{
		var args = uri.split('&');

		args.each( function(item, ix ){
			var param = item.split(':')[0];	
			var val   = item.split(':')[1];

			switch(param){
				case 'hint':
					cHintId = val;
					break;
				case 'de':
					cFrom = val.split(',');
					break;
				case 'para':
					cTo = val.split(',');
				case 'c':
					city = val;
					break;
			}
		});

        var dummyLatLng = new google.maps.LatLng();

		if(cFrom)
			fromLatLng  = dummyLatLng.decode( cFrom );
		if(cTo)
			toLatLng    = dummyLatLng.decode( cTo );

		if(cHintId)
			hintId  = cHintId.toInt(32);

        return [fromLatLng, toLatLng, hintId, city];

    }catch(err){
		trace(err);
        return null;
    }
}


/*
 * Minifies uri using bitly services  
 */   
getMiniuri =  function(url, cb){
		url = url.replace(/%E3/g, 'a').replace(/%C3%A3/g,'a').replace(String.fromCharCode(227),'a');
        var uri = "/api/miniurl/?full_uri={uri}";
        var request = new Request.JSON( {method: 'get', url: uri.substitute({'uri': escape(url)}), content_type: 'application/json', onFailure: trace, onSuccess: function(_nil,txt){
            var trim = JSON.decode( txt );
            cb(trim);
        }.bind(this)});

        request.get();
}

