/**
 * Copyright © 2007 Garmin Ltd. or its subsidiaries.
 *
 * Licensed under the Apache License, Version 2.0 (the 'License')
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an 'AS IS' BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * @version 1.0
 */

/**
 * Extend the Sample prototype to work in Google maps
 * @extends Sample
 */
Object.extend(Sample.prototype, {
    /**
     * @return {GLatLng} location for this sample
     */
    getLatLon: function() {
        return new GLatLng(this.getLat(), this.getLon());
    }
});

/**
 * Google implmentation of the MapController.  
 * 
 * @extends MapController
 */
Object.extend (MapController.prototype, {
    /**
     * Initializes the player with the map and controller 
     * @param mapString is the id of the map this is controlling
     * @param controllerString is the id of the Flash object 
     *  passing information to this object
     * @constructor
     * @member MapController
     */
    initialize: function(mapString, controller, usePositionMarker) {
        this.mapElement = $(mapString);
        this.controller = controller;
        this.usePositionMarker = true;
        
        this.polylines = new Array();
        this.markers = new Array();
        this.markerIndex = 0;

        this.timeToCheck = false;
        this.map = new GMap2( $(mapString) );
        this.map.setCenter(new GLatLng(58.062500, -95.677068), 4);
        
        GEvent.bind(this.map, "load", this, this.resizeOnLoad);
        new KeyboardHandler(this.map);
        
        window.onUnload = "GUnload()";
    },

    centerAndScale: function(lat, lon, scale) {
        if(scale == null) scale = 13;
        this.map.setCenter(new GLatLng(lat, lon), scale);
    },

	/**
	 * Adds an activity by drawing a line, placing a marker at the start, and moving the map to the bounds
	 */    
    addActivity: function(line) {
    	var polyline = this.addEncodedLine(line);
    	
        this.addMarker( polyline.getVertex(0) );
        this.bounds = this.findAnEncodedZoomLevel(polyline);
        this.setOnBounds( this.bounds );
    },
    
    /**
     * Add an encoded polyline to the map
     * @param line.points is the string that represents the line
     * @param line.levels is the string representing the levels of the line
     */
    addEncodedLine: function(line) {
    	log(line.points);
    	log(line.levels);
    	log(line.levels.length);
    
    	var polyline = new GPolyline.fromEncoded({
		  color: this.fillInTheBlanks(line.color),
		  weight: 2,
		  opacity: 0.4,
		  points: line.points,
		  levels: line.levels,
		  numLevels: line.numLevels,
		  zoomFactor: line.zoomFactor
		});
		this.polylines.push( polyline );
		this.map.addOverlay(polyline);
		return polyline;
    },
    
    /**
     * So let's say you want to make a line blue.  You'd tell google the color is '#0000ff'
     * And that kinda sucks.  Why?  It's a string.
     * 
     * So if I'm in AS and pass you 0x0000ff, after doing 0x0000ff.toString(16) you get 'ff';
     * Google doesn't like that.  They need it to be '#0000ff'
     * 
     * That's all this function does.  Returns the appropriate string for the color passed in
     */
    fillInTheBlanks: function(color) {
    	var answer = "#000000";
    	var temp = color.toString(16);
    	return answer.slice(0, answer.length-temp.length) + temp;
    },
    
 	/**
 	 * Finds a zoom level based on the information in the encoded polyline
 	 */
 	findAnEncodedZoomLevel: function(polyline) {
        var bounds = new GLatLngBounds(polyline.getVertex(0), polyline.getVertex(0));
 		var length = polyline.getVertexCount();

 		for(var i=1; i<length; i++) {
 			bounds.extend(polyline.getVertex(i));
 		}
 		
 		return bounds;
 	},
 
	/**
	 * Check the new dimensions of the map, and determine the bounds of the tracks
	 * Then set the map to zoom to that bound level
	 * @member MapController
	 * @private
	 */
    sizeAndSetOnBounds: function() {
        this.map.checkResize();
        this.setOnBounds( this.bounds );
    },
    
    setOnBounds: function(bounds) {
        this.map.setCenter( this.bounds.getCenter(), this.map.getBoundsZoomLevel(this.bounds) );
    },
    
    resizeOnLoad: function() {
        this.map.checkResize();
    },
    
    /**
     * Adds the defaul marker at the GLatLng location
     * @param point:GLatLng that defines where the marker should be
     */
    addMarker: function(point) {
        log('usePositionMarker: ' + this.usePositionMarker);
        if(this.usePositionMarker) {
            this.addMarkerWithIcon( point, MBIcon.getRedIcon() );
        } 
    },

    /**
     * Adds a marker to the point and icon specified
     */
    addStartFinishMarkers: function(track) {
        this.addMarkerWithIcon(track.getStart().getLatLon(), MBIcon.getGreenIcon());
        this.addMarkerWithIcon(track.getEnd().getLatLon(), MBIcon.getRedIcon());
    },

    /**
     * Adds a marker to the point and icon specified
     * 
     * @param point:GLatLng that defines where the marker should be
     * @param icon:GIcon that the marker should use
     */
    addMarkerWithIcon: function(point, icon) {
        log("Adding marker: " + point + icon);
        var gMarker = new GMarker( point, {icon: icon, clickable: false} );
        this.markers.push( gMarker );
        this.map.addOverlay( gMarker );
    },
    
    //TODO: moves all the markers, and stores the index of the current location
    moveMarker: function(lat, lon, index, activityNumber) {
        this.markers[activityNumber].setPoint( new GLatLng(lat.value, lon.value) );
        
        this.markerIndex = index;
        if( $("panMapOnMarkerMove").checked && this.timeToCheck )
            this.checkMoveMap(this.markerIndex);
            
        this.timeToCheck = !this.timeToCheck;
    },
    
    //TODO: only handles checking the first marker
    //TODO: plan ahead and move before we get off the map
    checkMoveMap: function(currentIndex) {
    	var lookAhead = Math.ceil(currentIndex + (this.map.getZoom()*.5));
        var point = this.polylines[0].getVertex( lookAhead );
        var bounds = this.map.getBounds();
    
        //if it doesn't have the point, pan the map    
        if( !bounds.contains(point) ) {
            this.map.panTo( point );
        }
    },

    toString: function() {
        return "Google Based Map Controller, managing " + this.tracks.length + " track(s)";
    }
});


/**
 * @class KeyboardHandler
 * Universal keyboard handler for the application.  Listens to events from all objects
 * 
 * @constructor 
 * @param {GMap2} map we will register our event handler on
 */
KeyboardHandler = function(map){}; //just here for jsdoc
var KeyboardHandler = Class.create();
KeyboardHandler.prototype = {
	initialize: function(map) {
		var manager = new GMarkerManager(map);

		var eggs = [];		        
        eggs.push(new GMarker(new GLatLng(37.794727, -121.912557), {clickable: false}));
        eggs.push(new GMarker(new GLatLng(37.843812, -121.981647), {clickable: false}));
        eggs.push(new GMarker(new GLatLng(37.775933, -122.408135), {clickable: false}));
        //add your own if you dare
        manager.addMarkers(eggs, 17);
        manager.refresh();
        
        new GKeyboardHandler(map);
	}
};


/**
 * @class BearingMarker
 * Represents a bearing marker.  This marker's location is thought to be a short distance from the 
 * origin, at a 90 degree angle to the bearing.
 * 
 * When the map zooms in/out, the actual location of the marker needs to change to be drawn closer/further
 * from the line
 * 
 * @requires Prototype
 * @requires MBIcon
 * 
 * @constructor
 * @param {GLatLng} origin where the bearing is actually measured
 * @param {Number} bearing the direction the person was travelling at this point
 * @param {GMap2} map on which the marker shall be drawn
 */
BearingMarker = function(origin, bearing, map){}; //just here for jsdoc
var BearingMarker = Class.create();
BearingMarker.prototype = {
	initialize: function(origin, bearing, map) {
		this.origin = origin;
		this.bearing = bearing;
		this.map = map;
		
		this.drawMarker();
	},
	
	/**
	 * Draws the marker on the map, and registers as a listener so it can move on zoom in/out
	 */
	drawMarker: function() {
        var pixelOrigin = this.map.fromLatLngToDivPixel(this.origin);
        
        var newX = pixelOrigin.x + 10*Math.cos(this.getAngle(this.bearing)-90);
        var newY = pixelOrigin.y + 10*Math.sin(this.getAngle(this.bearing)-90);
        var bearingPoint = new GPoint(newX, newY);
        
        var bearingIcon = MBIcon.getDirectionIcon(this.bearing);
        var bearingLatLng = this.map.fromDivPixelToLatLng(bearingPoint);
        var bearingMarker = new GMarker(bearingLatLng, {icon: bearingIcon, clickable: false});
        this.map.addOverlay(bearingMarker);
	},
	
	/**
	 * Return a guess of an angle based on the cardinal direction passed in.
	 * @param {String} direction that represents the cardinal direction of the measurement
	 * @return int that represents the direction passed in, 0 being North, 180 being south
	 */
	getAngle: function(direction) {
		var angle = 0;
		switch(direction) {
			case "n":
				angle = 0;
				break;
			case "e":
				angle = 90;
				break;
			case "s":
				angle = 180;
				break;
			case "w":
				angle = 270;
				break;
		}
		return angle;
	}
};

var MBIcon = {
    getDirectionIcon: function(direction) {
        var icon = new GIcon();
        icon.image = "./img/arrows/" + direction.toLowerCase() + ".png";
        icon.iconSize = new GSize(50, 50);
        icon.iconAnchor = new GPoint(25, 25);
        return icon;
    },
    
    getRedIcon: function() {
        var icon = new GIcon();
        icon.image = "./img/marker_red.png";
        return MBIcon._applyShadowAndStuff(icon);
    },
    
    getGreenIcon: function() {
        var icon = new GIcon();
        icon.image = "./img/marker_green.png";
        return MBIcon._applyShadowAndStuff(icon);
    },
    
    getBaseIcon: function() {
    	var baseIcon = new GIcon();
		baseIcon.shadow = "http://www.google.com/mapfiles/shadow50.png";
		baseIcon.iconSize = new GSize(20, 34);
		baseIcon.shadowSize = new GSize(37, 34);
		baseIcon.iconAnchor = new GPoint(9, 34);
		baseIcon.infoWindowAnchor = new GPoint(9, 2);
		baseIcon.infoShadowAnchor = new GPoint(18, 25);
        return baseIcon;
    },
    
    _applyShadowAndStuff: function(icon) {
        icon.iconSize = new GSize(12, 20);
        icon.shadow = "./img/marker_shadow.png";
        icon.shadowSize = new GSize(22, 20);
        icon.iconAnchor = new GPoint(6, 20);
        icon.infoWindowAnchor = new GPoint(5, 1);
        return icon;
    }
}

/**
 * Displays the log in Google's neat logging way.  Should be commented out when not in dev
 */
function log(message) {
    //GLog.write(message);
}