/**
*	jQuery Mapz v1.0
*
*	by Danny van Kooten - http://dannyvankooten.com
*	Last Modification: 20/06/2011
*
*	For more information, visit:
*	http://dannyvankooten.com/jquery-plugins/mapz/
*
*	Licensed under the Creative Commons Attribution 2.5 License - http://creativecommons.org/licenses/by/2.5/
*		- Free for use in both personal and commercial projects
*		- Attribution requires leaving author name, author link, and the license info intact.	
*
* mlindros notes:
*
* map-viewport is a class that is getting the div based off of the currently active element. This allows the code
* to work with multiple image maps, each which uses a separate id to distinguish, and to turn some images off while
* turning others on. this provides us with our nested diagram capability.
*
* The map-viewport div also has an ID that is based on the name of the diagram in order to control which
* image map is displayed.
*
* The map variable is the currently selected element. This will always have a unique id so we can distinguish
* image maps, but will all use the same CSS class: .draggable_map so we can set its appearance at will universally
*/
var count = 0;

(function( $ ){

    $.fn.mapz = function(options) {

        var settings = {
            'zoom'			:	false,
            'createmaps' 	:	false,
            'mousewheel' 	: 	false
        };

        if ( options ) {
            $.extend( settings, options );
        }

        var timeout;
        var interval = 50; //default
        var viewport = this.parent('.map-viewport');
        var map = this;
        var constraint = $(document.createElement('div')).addClass('mapz-constraint').css('position','absolute').appendTo(viewport);

        //mlindros: Variables for dragging through hotspots
        var mouse_x_orig = 0;
        var mouse_y_orig = 0;
        var hotspotclickstate = "NONE"; //Possible states are NONE, MOUSEDOWN
        var mousemoved = false;
        var movethreshold = 3; //if mouse moves more than 3 pixels it is a move, not a click
        var topOffset = 0; //The distance between the map Y and mouse Y when button is pressed
        var leftOffset = 0; //The distance between the map X and mouse X when button is pressed

        // Add current-level class to first map level
        map.find("img.level:first").addClass('current-level');

        //mlindros addition
        //Set all CSS properly for this image and
        //ensure that when the user resizes the screen that we recalculate constraints
        //so that the image appears and works properly within its new dimensions
        //NOTE: Important: The 'constraint' event is a custom event I created to fix an
        //issue that occurred after adding the Home tab interactive <ul> elements.
        //For some reason, the right edge of the image maps were leaking into garbage imagery
        //and the calls to createConstraint weren't effective anymore. This is a hack to get around
        //this. We bind the map element to the 'constraint' event, which only we can trigger.
        //When a user clicks on a tab, we call the trigger to this event to cause
        //Mapz to create the constraints for the map again on the fly. This causes the map
        //to display correctly!
        initDraggableMap();
        $(this).bind("constraint", createConstraint);
        $(window).resize(function() {
            createConstraint();
            positionArrows();
        });

        // Create constraint for current level.
        createConstraint();

        //Initialize user controls
        initControls();

        //mlindros code: Handle hotspot click events to allow dragging through hotspots
        $(".hotspot").live("mousedown", function(e) {
            e.preventDefault();
            //Just set up the vars to show that the hotspot mousedown event occurred
            hotspotclickstate = "MOUSEDOWN"; mouse_x_orig = e.pageX; mouse_y_orig = e.pageY;
            //Map the map's coords to the coords of the original mouse position
            topOffset = mouse_y_orig - map.position().top;
            leftOffset = mouse_x_orig - map.position().left;
            return false;
        });

        //Bind the mouse movement events to the entire document so users can drag beyond the viewport
        //This function manages the actual moving of the map image
        $(document).bind("mousemove mouseenter", function(e) {
            e.preventDefault();
            if (hotspotclickstate == "MOUSEDOWN") {
                //Determine if this is a click or a move
                if ( (e.pageX < mouse_x_orig - movethreshold) ||
                     (e.pageX > mouse_x_orig + movethreshold) ||
                     (e.pageY < mouse_y_orig - movethreshold) ||
                     (e.pageY > mouse_y_orig + movethreshold) ) {
                    mousemoved = true;
                    //Move map
                    handleMouseMapMove(e.pageX, e.pageY);
                }
            }
            return false;
        });

        //Based on if we are moving the map or clicking a hot spot, do the right thing
        $('.hotspot').live("mouseup", function(e) {
            e.preventDefault();
            if (hotspotclickstate == "MOUSEDOWN") {
                //Only display hotspot data if this is a click (not a map move)
                if (mousemoved == false) {
                    $(document).trigger("displayhotspot", this.id); //Causes poster.js to display hotspot data
                }
                mousemoved = false;
            }
            hotspotclickstate = "NONE"; //Make sure we know the mouse is not pressed
            return false;
        });

        //Make sure we unset the mouse so returning to the image doesn't move it
        $(document).bind("mouseup", function() {
            hotspotclickstate = "NONE";
            mousemoved = false;
        });
        
        // Is zooming enabled?
        if(settings.zoom) {

            $(document).keydown(function(e){
                // Pressed UP or DOWN key? -> zoom in or out accordingly
                if (e.keyCode == 38) { zoom('in'); return false; } else if(e.keyCode == 40) { zoom('out'); return false; }
            });
			
            if(settings.mousewheel) {
                map.bind('mousewheel', function(event, delta) {
                var dir = delta > 0 ? 'in' : 'out';
                zoom(dir);
                return false;
                });
            }

            //mlindros code:
            //Add handlers for when users click our plus and minus icons
            $('.plus').click(function() { zoom('in'); return false; });
            $('.minus').click(function() { zoom('out'); return false; });
			
            // Create HTML maps for zoomed levels?
            if(settings.createmaps) createMaps();
        }

        //JQuery makes the map div draggable using the calculated constraint
        map.draggable({
            containment : constraint
        });
		
        //Function that calculates (scales) the image map hotspots for the zoomed in image
        function createMaps(){
            var htmlmap = viewport.children('map');
            var scale = 1;
			var i = 0;
			
			// Loop through zoom levels
			map.children('.level').each(function() {
				i++;
				
				// If current map level, return. This one should have a map already.
				if($(this).hasClass('current-level')) return;
				
				// Get scales for map to create
				scale = $(this).width() / map.width();
				
				// Create new map element
				var newmap = $(document.createElement('map')).attr('name',map.attr('id') + '-map-' + i);
				
				// Calculate new coords for each area element
				//noinspection JSCheckFunctionSignatures
                htmlmap.children('area').each(function() {
					var newArea = $(this).clone();
					
					var coords = $(this).attr('coords').split(',');

                    for (var c in coords) {
                        coords[c] = Math.ceil(coords[c] * scale);
                    }
					
					newArea.attr('coords',coords).appendTo(newmap);
				});
				
				// Append new map to viewport and hook to zoom level
				newmap.appendTo(viewport);
				$(this).attr('usemap','#' + map.attr('id') + '-map-' + i);
			});
		}
		
		// Create a constraint div so map can't be dragged out of view.
		function createConstraint()
		{
			constraint.css({
				left : -(map.width()) + viewport.width(),
				top : -(map.height()) + viewport.height(),
				width : 2 * map.width() - viewport.width(),
				height : 2 * map.height() - viewport.height()
			});
			
			// Check if map is currently out of bounds, revert to closest position if so
			if(map.position().left < constraint.position().left) map.css('left',constraint.position().left);
			if(map.position().top < constraint.position().top) map.css('top',constraint.position().top);
		}
		
		//Perform image zooming and scale hotspots
		function zoom(direction) {
			//mlindros code: NOTE: This function is called by the keyboard handler for each image. This code makes sure
			//that it only affects the image map that is showing. If this map is hidden simply return.
			//This preserves the zoom level of all hidden maps and alters only the showing map.
			//Also, all maps maintain their position and zoom levels as they are only hidden and shown.
			if (viewport.is(':hidden')) return;
			
			var currentlvl = map.find('img.current-level');
            var targetlvl = null;
			
			var currentIndex = 0;
			var arrayImgs = map.find('img.level').get();
			for (var yy = 0; yy < arrayImgs.length; yy++) {
				if ($(map.find('img.level').get(yy)).hasClass('current-level')) {
					currentIndex = yy;
					break;
				}
			}
			
			//Set direction and check if there is a deeper level to zoom to.	
			switch(direction) {
			case 'in':
					if ((currentIndex + 1) == arrayImgs.length) return;
					targetlvl = $((map).find('img.level').get(currentIndex+1));
				break;
			case 'out':
					if (currentIndex == 0) return;
					targetlvl = $((map).find('img.level').get(currentIndex-1));
				break;
			}

			targetlvl.parent('div').show(); //Show target so sizes are available
			
			//mlindros calcs for scaling and centering zoomed images
			var winWidth = viewport.width();
			var winHeight = viewport.height();
			var currentWidth = currentlvl.width();
			var currentHeight = currentlvl.height();
			var targetWidth = targetlvl.width();
			var targetHeight = targetlvl.height();
			var viewport_offset = viewport.offset();
			var offset = map.offset();
			offset = { 
				top: viewport_offset.top - offset.top + winHeight / 2 + 4,
				left: viewport_offset.left - offset.left + winWidth / 2 + 4
			};
			
			// Relative offsets, relative to the center!
			offset.top = offset.top / currentHeight;
			offset.left = offset.left / currentWidth;
			offset = {
				top: -offset.top * targetHeight + winHeight / 2,
				left: -offset.left * targetWidth + winWidth / 2
			};

			// Switch levels and re-zoom to viewed position
			currentlvl.removeClass('current-level');
			currentlvl.parent('div').removeClass('current-level');
			targetlvl.addClass('current-level');
			if (!targetlvl.hasClass('maphilighted')) {
				targetlvl.maphilight(); //only highlight if not already highlighted
			}
			currentlvl.parent('div').hide();  //Hide the maphilight wrapped div so image shows properly
			
			var pos = map.position();
			map.css({
				left : offset.left, 
				top : offset.top,
				width : targetlvl.width(),
				height : targetlvl.height()
			});
			
			// Since we zoomed to another level we need to recreate constraint div and adjust buttons
			createConstraint();
			positionArrows();
		}
   
		/**
		 * mlindros code
         * This function will set CSS width and height for all styles that use images
		 * to keep constraints and visuals in control. By automatically calculating these
		 * settings the poster developers do not need to manually alter CSS files for this.
		 * Additionally, nested image support requires supporting different image sizes for
		 * each diagram but they all use the same CSS classes. This allows us to dynamically
		 * adjust CSS to the correct settings for each image based on the image's actual size.
		 */
		function initDraggableMap(){
			var MIN = 300;
			var MAXWIDTH = 1076;
			var MAXHEIGHT = 1020;
			
			//Set map CSS based on current image size obtained from HTML map
			var currentmap = map.children(".current-level");
			map.css({
				width : currentmap.width(),
				height : currentmap.height()
			});

			//Set viewport CSS to ensure min and max sizes dynamically. Keeping these out of CSS file on purpose
			//Must be same as original image size or less!!! Can't show more than original size.
			viewport.css({"min-width" :MIN});           /* Makes sure that browser resizing doesn't over shrink image */
			viewport.css({"min-height":MIN});           /* Makes sure that browser resizing doesn't over shrink image */
			$("#hotspot-data").css({"min-height":MIN}); /* Must be same as .map-viewport size. Keeps it aligned. */
			if (currentmap.width() < MAXWIDTH) {
				viewport.css({"max-width":currentmap.width()});
			} else {
				viewport.css({"max-width":MAXWIDTH});
			}

			if (currentmap.height() < MAXHEIGHT) {
				viewport.css({"max-height":currentmap.height()});
			} else {
				viewport.css({"max-height":MAXHEIGHT});
				$("#hotspot-data").css({"max-height":MAXHEIGHT}); /* Must be same as .map-viewport size. Keeps it aligned. */
			}
		}

        /**
         * mlindros code
         * Now we need to manage the arrows and zoom controls for the poster
         */
        function initControls() {
            var up = viewport.find('.arrow-up');
            var down = viewport.find('.arrow-down');
            var left = viewport.find('.arrow-left');
            var right = viewport.find('.arrow-right');

            //Position arrows. Top and left are automatic via css, but bottom and right need some calcs
            positionArrows();

            var agent = navigator.userAgent;
            if ( (agent.match(/iPhone/i)) || (agent.match(/iPad/i)) || (agent.match(/iPod/i)) ) {
                interval = 5; //iOS devices are slower so less pausing is better
                //Set listeners for each arrow.. bind on click or mousedown
                $(up).bind("mousedown", function() { handleMapMove('up'); return false; });
                $(down).bind("mousedown", function() { handleMapMove('down'); return false; });
                $(left).bind("mousedown", function() { handleMapMove('left'); return false; });
                $(right).bind("mousedown", function() { handleMapMove('right'); return false; });
            } else {
                interval = 50; //need to slow PCs down so scroll looks better
                //PC: Set listeners for each arrow.. bind on click or mousedown
                $(up).bind("mouseover", function() { handleMapMove('up'); return false; });
                $(down).bind("mouseover", function() { handleMapMove('down'); return false; });
                $(left).bind("mouseover", function() { handleMapMove('left'); return false; });
                $(right).bind("mouseover", function() { handleMapMove('right'); return false; });
            }

            //Stop scrolling if mouse is up or leaves arrow
            $('.arrow').bind("mouseout", function() { clearInterval(timeout); return false; });
        }

      /**
       * mlindros code
       * This function is called by all of the handlers to move the map. The only difference
       * is that each calls with a different direction. This way we know which way to scroll the map.
       */
        function handleMapMove(direction) {
            var currentmap = map.children(".current-level");
            var position;
            var scrollPix = 25;

            //setInterval allows us to respond to the mouse button being held down for scrolling
            timeout = setInterval(function() {
                var origTop = map.position().top;
                var origLeft = map.position().left;
                var newTop = map.position().top;
                var newLeft = map.position().left;
                switch(direction) {
                    case 'up':
                        newTop = (map.position().top + scrollPix);
                        break;
                    case 'down':
                        newTop = (map.position().top - scrollPix);
                        break;
                    case 'left':
                        newLeft = (map.position().left + scrollPix);
                        break;
                    case 'right':
                        newLeft = (map.position().left - scrollPix);
                        break;
                }

                //Make sure map is within proper constraints so we don't allow over scrolling
                if (newTop < constraint.position().top) newTop=constraint.position().top;
                if (newLeft < constraint.position().left) newLeft=constraint.position().left;
                if (newLeft > 0) newLeft=0;
                if (newTop > 0) newTop=0;

                //Set map position manually using x.y coords
                map.css({
                    top: newTop+"px",
                    left: newLeft+"px"
                });
            }, interval);
        }

		/**
         * mlindros code
         * This function is called whenever the browser moves and during initialization to keep
		 * all buttons in the proper position. This also occurs when a new image is displayed based on
		 * a tab, breadcrumb, or other click.
         */
        function positionArrows() {
			if (viewport.is(':hidden')) return;
			
            var up = viewport.find('.arrow-up');
            var down = viewport.find('.arrow-down');
            var left = viewport.find('.arrow-left');
            var right = viewport.find('.arrow-right');
            var plus = viewport.find('.plus');
            var minus = viewport.find('.minus');

            //Position arrows. Top and left are automatic via css, but bottom and right need some calcs
            down.css({ top: viewport.height() - 25 +"px" });
            right.css({ left: viewport.width() - 24 +"px" });
            plus.css({ top: viewport.position().top -25 +"px", left: viewport.width() - 55 +"px" });
            minus.css({ top: viewport.position().top -25 +"px", left: viewport.width() - 28 +"px" });
			
			// The current zoom level is at its lowest point: Do not show minus button
			if ($(viewport).find("img.level:first").hasClass("current-level")) { minus.hide(); } else {	minus.show(); }
			
			// The current zoom level is at its highest point: Do not show plus button
			var currentIndex = 0;
			var arrayImgs = map.find('img.level').get();
			for (var yy = 0; yy < arrayImgs.length; yy++) {
				if ($(map.find('img.level').get(yy)).hasClass('current-level')) {
					currentIndex = yy;
					break;
				}
			}
			if ((currentIndex + 1) == arrayImgs.length) { plus.hide(); } else {	plus.show(); }
			
			// The image file x is not larger than the viewport x: Do not show left/right arrow buttons
			if (map.width() <= viewport.width()) { left.hide();	right.hide(); } else { left.show();	right.show(); }
			
			// The image file y is not larger than the viewport y: Do not show up/down arrow buttons
			if (map.height() <= viewport.height()) { up.hide(); down.hide(); } else { up.show(); down.show(); }
        }

        /**
         * mlindros code
         * This function is called when a user clicks through a hot spot to move the map.
         */
        function handleMouseMapMove(currX, currY) {
            //Calculate current map coords by taking current mouse coords and factoring in the offset
            var newTop = currY - topOffset;
            var newLeft = currX - leftOffset;

            if (newTop < constraint.position().top) newTop=constraint.position().top;
            if (newLeft < constraint.position().left) newLeft=constraint.position().left;
            if (newLeft > 0) newLeft=0;
            if (newTop > 0) newTop=0;

            //Set map position manually using x.y coords
            map.css({
                top: newTop+"px",
                left: newLeft+"px"
            });
        }
  };
})( jQuery );