/**
 * Mapbox, the jQuery Map
 * jQuery Map Plugin
 * Version 0.6.0 beta
 * Author Abel Mohler
 * Released with the MIT License: http://www.opensource.org/licenses/mit-license.php
 */
(function($) {
    $.fn.mapbox = function(o) {
        if (typeof o == 'string') {
            var instance = $(this).data('mapbox'), args = Array.prototype.slice.call(arguments, 1);
            return instance[o].apply(instance, args);
        } else {
            return this.each(function() {
                $(this).data('mapbox', new $.mapbox(this, o));
            });
        }
    };

    var defaults = {
        zoom: true, // does map zoom?
        pan: true, // does map move side to side and up to down?
        lazyloadImages: true,
        defaultLayer: 0, // starting at 0, which layer shows up first
        layerSplit: 4, // how many times to split each layer as a zoom level
        defaultX: null, // default positioning on X-axis
        defaultY: null, // default positioning on Y-axis
        callBefore: function(layer, xcoord, ycoord, viewport) {}, // this callback happens before dragging of map starts
        callAfter: function(layer, xcoord, ycoord, viewport) {}, // this callback happens at end of drag after map is released "mouseup"
        callDrag: function(layer, xcoord, ycoord, viewport) {}, // this callback happens while dragging the map
        beforeZoom: function(layer, xcoord, ycoord, viewport) {}, // callback before a zoom happens
        afterZoom: function(layer, xcoord, ycoord, viewport) {} // callback after zoom has completed
    }

    $.mapbox = function(e, o) {
        var self = this;
        self.o = $.extend(defaults, o || {});//inherit properties

        self.viewport = $(e);
        self.layers = self.viewport.find(">div");
        self.mapHeight = self.viewport.height();
        self.mapWidth = self.viewport.width();
        self.mapmove = false;
        self.first = true;
        self.currentZoom = self.o.defaultLayer;

        self.viewport.css({
            overflow: "hidden",
            position: "relative"
        });

        self.layers.css({
            position: "absolute"
        });

        self.currentMapLayer = self.layers
        .eq(self.o.defaultLayer).css({
            display: "block",
            left: "",
            top: ""
        }).addClass("current-map-layer");

        self.layers.each(function() {
            this.defaultWidth = $(this).width();
            this.defaultHeight = $(this).height();
        })
        .append('<div class="map-layer-mask"></div>')
        .find(".map-layer-mask").css({
            position: "absolute",
            left: "0",
            top: "0",
            background: "white",// omg, horrible hack,
            opacity: "0",// but only way IE will not freak out when
            filter: "alpha(opacity=0)"// mouseup over IMG tag occurs after mousemove event
        });

        if (o.defaultLayer > 0) {
            self.currentMapLayer.find(".map-layer-mask").width(self.currentMapLayer.width()).height(self.currentMapLayer.height());
        }

        self.viewport.find(">div:not(.current-map-layer)").hide();
        if(self.o.defaultX == null) {
            self.o.defaultX = Math.floor((self.mapWidth / 2) - (self.currentMapLayer.width() / 2));
            if(self.o.defaultX > 0) self.o.defaultX = 0;
        }
        if(self.o.defaultY == null) {
            self.o.defaultY = Math.floor((self.mapHeight / 2) - (self.currentMapLayer.height() / 2));
            if(self.o.defaultY > 0) self.o.defaultY = 0;
        }

        self.xPos = 0 - self.o.defaultX;
        self.yPos = 0 - self.o.defaultY;

        self.mapStartX = self.o.defaultX;
        self.mapStartY = self.o.defaultY;
        self.clientStartX = 0;
        self.clientStartY = 0;
        self.clickDefault = true;
        self.weveMoved = false;

        self.currentMapLayer.css({
            left: self.o.defaultX + "px",
            top: self.o.defaultY + "px"
        });

        /**
         * Event Handling and Callbacks
         */
        self.viewport.mousedown(function() {
            var x = self.currentMapLayer[0].style.left, y = self.currentMapLayer[0].style.top;
            x = _makeCoords(x);
            y = _makeCoords(y);
            self.o.callBefore.call(self, self.currentMapLayer[0], x, y, self.viewport);
            self.mapmove = true;
            self.first = true;
            return false;
        });

        $(document).mouseup(function() {
            var x = self.currentMapLayer[0].style.left, y = self.currentMapLayer[0].style.top;
            x = _makeCoords(x);
            y = _makeCoords(y);
            self.o.callAfter.call(self, self.currentMapLayer[0], x, y, self.viewport);
            self.mapmove = false;

            if(self.weveMoved) {
                self.clickDefault = false;
            }
            self.weveMoved = false;

            return false;
        });

        $(document).mousemove(function(e) {
            if (self.mapmove && self.o.pan) {
                if (self.first) {
                    self.clientStartX = e.clientX;
                    self.clientStartY = e.clientY;
                    self.mapStartX = self.currentMapLayer[0].style.left.replace(/px/, "");
                    self.mapStartY = self.currentMapLayer[0].style.top.replace(/px/, "");
                    self.first = false;
                } else {
                    self.weveMoved = true;
                }

                var limitX = 0, limitY = 0;
                if (self.mapWidth < self.currentMapLayer.width()) limitX = self.mapWidth - self.currentMapLayer.width();
                if (self.mapHeight < self.currentMapLayer.height()) limitY = self.mapHeight - self.currentMapLayer.height();
                var mapX = self.mapStartX - (self.clientStartX - e.clientX);
                mapX = (mapX > 0) ? 0 : mapX;
                mapX = (mapX < limitX) ? limitX : mapX;
                var mapY = self.mapStartY - (self.clientStartY - e.clientY);
                mapY = (mapY > 0) ? 0 : mapY;
                mapY = (mapY < limitY) ? limitY : mapY;
                self.currentMapLayer.css({
                    left: mapX + "px",
                    top: mapY + "px"
                });
                self.xPos = _makeCoords(self.currentMapLayer[0].style.left);
                self.yPos = _makeCoords(self.currentMapLayer[0].style.top);

                self.o.callDrag.call(self, self.currentMapLayer[0], mapX, mapY, self.viewport);
                _lazyloadImages.apply(self);
            }
        });

        self.images = [];

        //deferred, load images in hidden layers
        if (!self.o.lazyloadImages) {
            $(window).load(function() {
                self.layers.find('img').each(function() {
                    $("<img>").attr("src", this.src);
                });
            });
        } else {
            self.layers.find('img').each(function() {
                if (self.isVisible(this)) {
                    return;
                }
                var img = this;
                $(img)
                .attr("original", $(img).attr("src"))
                .removeAttr("src")
                .one("appear", function() {
                    if (!this.loaded) {
                        $("<img />")
                            .bind("load", function() {
                                $(img)
                                    .hide()
                                    .attr("src", $(img).attr("original"))
                                    .show();
                                img.loaded = true;
                            })
                            .attr("src", $(img).attr("original"));
                    };
                });
                self.images.push(img);
            });
        }
    };

    $.mapbox.prototype = {
        isVisible: function(element, mode) {
            var x1 = this.viewport.offset().left, x2 = x1 + this.viewport.width(),
                y1 = this.viewport.offset().top, y2 = y1 + this.viewport.height();
            var l = $(element).offset().left, r = l + $(element).width(),
                t = $(element).offset().top, b = t + $(element).height();

            if (mode == 'fit') {
                return (l < x1 && x2 < r
                    && t < y1 && y2 < b);
            } else {
                return (
                        (y1 >= t && y1 <= b) ||	// Top edge touching
                        (y2 >= t && y2 <= b) ||	// Bottom edge touching
                        (y1 < t && y2 > b)		// Surrounded vertically
                    ) && (
                        (x1 >= l && x1 <= r) ||	// Left edge touching
                        (x2 >= l && x2 <= r) ||	// Right edge touching
                        (x1 < l && x2 > r)		// Surrounded horizontally
                    );
            }
        },
        makeVisible: function(element, margin, animate) {
            margin = margin || {};
            var x1 = this.viewport.offset().left + (margin.left != undefined ? margin.left : 0),
                x2 = x1 + this.viewport.width() - (margin.right != undefined ? margin.right : 0),
                y1 = this.viewport.offset().top + (margin.top != undefined ? margin.top : 0),
                y2 = y1 + this.viewport.height() - (margin.bottom != undefined ? margin.bottom : 0);

            element = $(element);

            var l = element.offset().left, r = l + element.width(),
                t = element.offset().top, b = t + element.height();

            if (l < x1) {
                x = 0 - x1 + l;
            } else if (r > x2) {
                x = r - x2;
            } else {
                x = 0;
            }

            if (t < y1) {
                y = 0 - y1 + t;
            } else if (b > y2) {
                y = b - y2;
            } else {
                y = 0;
            }

            _move.call(this, x, y, animate);
        },
        getClickDefault: function() {
            return this.clickDefault;
        },
        setClickDefault: function(bool) {
            this.clickDefault = bool;
        },
        layer: function() {
            return this.currentMapLayer.get(0);
        },
        zoom: function(distance) {
		    if (distance == undefined) {
			    return this.currentZoom;
			}
            distance = distance || 1;
            return _zoom.call(this, distance);
        },
        back: function(distance) {
            distance = distance || 1;
            return _zoom.call(this, 0 - distance);
        },
        left: function(amount, animate) {
            amount = amount || 10;
            return _move.call(this, 0 - amount, 0, animate);
        },
        right: function(amount, animate) {
            amount = amount || 10;
            return _move.call(this, amount, 0, animate);
        },
        up: function(amount, animate) {
            amount = amount || 10;
            return _move.call(this, 0, 0 - amount, animate);
        },
        down: function(amount, animate) {
            amount = amount || 10;
            return _move.call(this, 0, amount, animate);
        },
        center: function(coords, animate) {
            coords = coords || {
                x: this.currentMapLayer.width() / 2,
                y: this.currentMapLayer.height() / 2
            }
            var newX = coords.x - (this.viewport.width() / 2), newY = coords.y - (this.viewport.height() / 2);
            _position.call(this, newX, newY, animate);
        },
        centerByElement: function(element, animate) {
            var x = $(element).offset().left + ($(element).width() / 2),
                y = $(element).offset().top + ($(element).height() / 2);

            var positionTop = y - this.currentMapLayer.offset().top,//jQuery normalizes pageX and pageY for us.
                positionLeft = x - this.currentMapLayer.offset().left,
                //recalculate this position on current layer as a percentage
                percentTop = positionTop / this.currentMapLayer.height(),
                percentLeft = positionLeft / this.currentMapLayer.width();

            //and center
            this.center({x: this.currentMapLayer.width() * percentLeft, y: this.currentMapLayer.height() * percentTop}, animate);
        },
        zoomTo: function(level) {
            var distance = Math.round((level - this.currentZoom) / (1 / this.o.layerSplit));
            _zoom.call(this, distance);
        }
    };

    function _lazyloadImages() {
        var tmp = [];
        
        for (var i = 0; i < this.images.length; i++) {
            if (this.isVisible(this.images[i])) {
                $(this.images[i]).trigger("appear");
            } else {
                tmp.push(this.images[i]);
            }
        }

        this.images = tmp;
    }

    function _zoom(distance) {
        if(!this.o.zoom) return false;

        if(distance === 0) distance = 0;
        else distance = distance || 1;

        var limit = this.layers.length - 1;

        this.o.beforeZoom.call(this, this.currentMapLayer[0], this.xPos, this.yPos, this.viewport);

        var move = this.currentZoom, eq = move;
        move += (distance / this.o.layerSplit);
        if(move < 0) move = 0;
        if(move > limit) move = limit;
        eq = Math.ceil(move);
        var movement = (this.currentZoom == move) ? false : true;
        this.currentZoom = move;

        var oldWidth = this.currentMapLayer.width(), oldHeight = this.currentMapLayer.height();
        var xPercent = ((this.viewport.width() / 2) + this.xPos) / oldWidth,
        yPercent = ((this.viewport.height() / 2) + this.yPos) / oldHeight;

        if ((this.o.layerSplit > 1 && eq > 0)) {
            var percent = move - (eq -1), thisX = this.layers.eq(eq)[0].defaultWidth, thisY = this.layers.eq(eq)[0].defaultHeight, lastX = this.layers.eq(eq - 1).width(), lastY = this.layers.eq(eq - 1).height();
            var differenceX = thisX - lastX, differenceY = thisY - lastY, totalWidth = lastX + (differenceX * percent), totalHeight = lastY + (differenceY * percent);

            this.layers.eq(eq).width(totalWidth).find(".map-layer-mask").width(totalWidth).height(totalHeight);
            this.layers.eq(eq).height(totalHeight);
        }

        //left and top adjustment for new zoom level
        var newLeft = (this.layers.eq(eq).width() * xPercent) - (this.viewport.width() / 2),
        newTop = (this.layers.eq(eq).height() * yPercent) - (this.viewport.height() / 2);

        newLeft = 0 - newLeft;
        newTop = 0 - newTop;

        var limitX = this.viewport.width() - this.layers.eq(eq).width(),
        limitY = this.viewport.height() - this.layers.eq(eq).height();

        if(newLeft > 0) newLeft = 0;
        if(newTop > 0) newTop = 0;
        if(newLeft < limitX) newLeft = limitX;
        if(newTop < limitY) newTop = limitY;

        this.xPos = 0 - newLeft;
        this.yPos = 0 - newTop;

        this.layers.removeClass("current-map-layer").hide();
        this.currentMapLayer = this.layers.eq(eq).css({
            left: newLeft + "px",
            top: newTop + "px",
            display: "block"
        }).addClass("current-map-layer");

        this.o.afterZoom.call(this, this.currentMapLayer[0], this.xPos, this.yPos, this.viewport);
        _lazyloadImages.apply(this);

        return movement;
    }

    function _move(x, y, animate) {
        var o = this.currentMapLayer.offset();
        var limitX = 0, limitY = 0,nodeWidth = this.currentMapLayer.width(), nodeHeight = this.currentMapLayer.height();

        if(this.mapWidth < nodeWidth) limitX = this.mapWidth - nodeWidth;
        if(this.mapHeight < nodeHeight) limitY = this.mapHeight - nodeHeight;

        var left = 0 - (this.xPos + x), top = 0 - (this.yPos + y);

        left = (left > 0) ? 0 : left;
        left = (left < limitX) ? limitX : left;
        top = (top > 0) ? 0 : top;
        top = (top < limitY) ? limitY : top;

        this.xPos = 0 - left;
        this.yPos = 0 - top;

        if (animate) {
            var self = this;
            this.currentMapLayer.animate(
                {
                    left: left + "px",
                    top: top + "px"
                },
                {
                    duration: 'fast',
                    complete: function() {
                        _lazyloadImages.apply(self);
                    },
                    step: function() {
                        self.o.callDrag.call(self, self.currentMapLayer[0], left, top, self.viewport);
                    }
                }
            );
        } else {
            this.currentMapLayer.css({
                left: left + "px",
                top: top + "px"
            });

            this.o.callDrag.call(this, this.currentMapLayer[0], left, top, this.viewport);
            _lazyloadImages.apply(this);
        }

        return o.left != left || o.top != top;
    }

    function _position(x, y, animate) {
        x = 0 - x;
        y = 0 - y;

        var limitX = 0 - (this.currentMapLayer.width() - this.viewport.width());
        var limitY = 0 - (this.currentMapLayer.height() - this.viewport.height());

        if(x > 0) x = 0;
        if(y > 0) y = 0;
        if(x < limitX) x = limitX;
        if(y < limitY) y = limitY;

        this.xPos = 0 - x;
        this.yPos = 0 - y;

        if (animate) {
            var self = this;
            this.currentMapLayer.animate(
                {
                    left: x + "px",
                    top: y + "px"
                },
                {
                    duration: 'fast',
                    complete: function() {
                        _lazyloadImages.apply(self);
                    },
                    step: function() {
                        self.o.callDrag.call(self, self.currentMapLayer[0], left, top, self.viewport);
                    }
                }
            );
        } else {
            this.currentMapLayer.css({
                left: x + "px",
                top: y + "px"
            });

            this.o.callDrag.call(this, this.currentMapLayer[0], x, y, this.viewport);
            _lazyloadImages.apply(this);
        }
    }

    function _makeCoords(s) {
        s = s.replace(/px/, "");
        s = 0 - s;
        return s;
    }

})(jQuery);
