// Dependencies

/*
jquery.event.drag.js ~ v1.5 ~ Copyright (c) 2008, Three Dub Media (http://threedubmedia.com)
Licensed under the MIT License ~ http://threedubmedia.googlecode.com/files/MIT-LICENSE.txt
*/
(function(E){E.fn.drag=function(L,K,J){if(K){this.bind("dragstart",L)}if(J){this.bind("dragend",J)}return !L?this.trigger("drag"):this.bind("drag",K?K:L)};var A=E.event,B=A.special,F=B.drag={not:":input",distance:0,which:1,dragging:false,setup:function(J){J=E.extend({distance:F.distance,which:F.which,not:F.not},J||{});J.distance=I(J.distance);A.add(this,"mousedown",H,J);if(this.attachEvent){this.attachEvent("ondragstart",D)}},teardown:function(){A.remove(this,"mousedown",H);if(this===F.dragging){F.dragging=F.proxy=false}G(this,true);if(this.detachEvent){this.detachEvent("ondragstart",D)}}};B.dragstart=B.dragend={setup:function(){},teardown:function(){}};function H(L){var K=this,J,M=L.data||{};if(M.elem){K=L.dragTarget=M.elem;L.dragProxy=F.proxy||K;L.cursorOffsetX=M.pageX-M.left;L.cursorOffsetY=M.pageY-M.top;L.offsetX=L.pageX-L.cursorOffsetX;L.offsetY=L.pageY-L.cursorOffsetY}else{if(F.dragging||(M.which>0&&L.which!=M.which)||E(L.target).is(M.not)){return }}switch(L.type){case"mousedown":E.extend(M,E(K).offset(),{elem:K,target:L.target,pageX:L.pageX,pageY:L.pageY});A.add(document,"mousemove mouseup",H,M);G(K,false);F.dragging=null;return false;case !F.dragging&&"mousemove":if(I(L.pageX-M.pageX)+I(L.pageY-M.pageY)<M.distance){break}L.target=M.target;J=C(L,"dragstart",K);if(J!==false){F.dragging=K;F.proxy=L.dragProxy=E(J||K)[0]}case"mousemove":if(F.dragging){J=C(L,"drag",K);if(B.drop){B.drop.allowed=(J!==false);B.drop.handler(L)}if(J!==false){break}L.type="mouseup"}case"mouseup":A.remove(document,"mousemove mouseup",H);if(F.dragging){if(B.drop){B.drop.handler(L)}C(L,"dragend",K)}G(K,true);F.dragging=F.proxy=M.elem=false;break}return true}function C(M,K,L){M.type=K;var J=E.event.handle.call(L,M);return J===false?false:J||M.result}function I(J){return Math.pow(J,2)}function D(){return(F.dragging===false)}function G(K,J){if(!K){return }K.unselectable=J?"off":"on";K.onselectstart=function(){return J};if(K.style){K.style.MozUserSelect=J?"":"none"}}})(jQuery);


(function ($) {
    function init(plot) {
        var selection = {
              selecting: false,             // Is the selection being done?
              show: false,                  // Is the selection visible?
              resizeMode: 0,                // Which side or corner is being resized?
                                            // 0. None
                                            // 1. Left border      5. Left-top corner
                                            // 2. Right border     6. Right-top corner
                                            // 3. Top border       7. Left-bottom corner
                                            // 4. Bottom border    8. Right-bottom corner
                                            //
              draggable: false,             // Is the mouse over the selection?
              first: { x: null, y: null },  // Selection corners
              second: { x: null, y: null }
            },
            fst = selection.first,
            snd = selection.second,
            cursorPos = { x: -1, y: -1 }, // Cursor position
            dragFrom,                     // Previous point during drag
            minSize = 1,                  // Minimum selection size
            prevSel;                      // Save the previous selection


        // Public methods
        plot.clearSelection = clearSelection;
        plot.setSelection = setSelection;
        plot.getSelection = getSelection;
        plot.isSelecting = function() { return selection.selecting; };
        plot.hasSelection = function() {
            return selectionIsSane();
        };


        /**
         * Returns the current selection, in plot coordinates.
         */
        function getSelection() {
            if (!selectionIsSane())
                return null;

            var x1 = Math.min(selection.first.x, selection.second.x),
                x2 = Math.max(selection.first.x, selection.second.x),
                y1 = Math.max(selection.first.y, selection.second.y),
                y2 = Math.min(selection.first.y, selection.second.y);

            var r = {};
            var axes = plot.getAxes();
            if (axes.xaxis.used)
                r.xaxis = { from: axes.xaxis.c2p(x1), to: axes.xaxis.c2p(x2) };
            if (axes.x2axis.used)
                r.x2axis = { from: axes.x2axis.c2p(x1), to: axes.x2axis.c2p(x2) };
            if (axes.yaxis.used)
                r.yaxis = { from: axes.yaxis.c2p(y1), to: axes.yaxis.c2p(y2) };
            if (axes.y2axis.used)
                r.y2axis = { from: axes.y2axis.c2p(y1), to: axes.y2axis.c2p(y2) };
            return r;
        }


        /**
         * Sets the selection (given in plot coordiantes), respecting the selection mode.
         * Fires the 'plotselected' event if preventEvent is false.
         */
        function setSelection(ranges, preventEvent) {
            var axis, range, axes = plot.getAxes();
            var o = plot.getOptions();
            backupSel();

            if (o.selection.mode == "y") {
                selection.first.x = 0;
                selection.second.x = plot.width();
            }
            else {
                axis = ranges["xaxis"]? axes["xaxis"]: (ranges["x2axis"]? axes["x2axis"]: axes["xaxis"]);
                range = ranges["xaxis"] || ranges["x2axis"] || { from:ranges["x1"], to:ranges["x2"] };
                selection.first.x = clamp(0, axis.p2c(Math.min(range.from, range.to)), plot.width());
                selection.second.x = clamp(0, axis.p2c(Math.max(range.from, range.to)), plot.width());
            }

            if (o.selection.mode == "x") {
                selection.first.y = 0;
                selection.second.y = plot.height();
            }
            else {
                axis = ranges["yaxis"]? axes["yaxis"]: (ranges["y2axis"]? axes["y2axis"]: axes["yaxis"]);
                range = ranges["yaxis"] || ranges["y2axis"] || { from:ranges["y1"], to:ranges["y2"] };
                selection.first.y = clamp(0, axis.p2c(Math.min(range.from, range.to)), plot.height());
                selection.second.y = clamp(0, axis.p2c(Math.max(range.from, range.to)), plot.height());
            }

            if (selectionIsSane()) {
                selection.show = true;
                plot.triggerRedrawOverlay();
                if (!preventEvent) {
                    triggerSelectedEvent();
                }
            } else {
                restoreSel();
            }
        }


        /**
         * Fires the 'plotselected' event.
         */
        function triggerSelectedEvent() {
            var r = getSelection();
            plot.getPlaceholder().trigger("plotselected", [r]);
        }


        /**
         * Clears the selection.
         * Fires the 'plotunselected' event if preventEvent is false.
         */
        function clearSelection(preventEvent) {
            if (selection.show) {
                selection.show = false;
                selection.first.x = null;
                selection.first.y = null;
                selection.second.x = null;
                selection.second.y = null;

                plot.triggerRedrawOverlay();
                if (!preventEvent) {
                    plot.getPlaceholder().trigger("plotunselected", []);
                }
            }
        }


        /**
         * Convert mouse coordinates to canvas coordinates.
         */
        function toCanvas(x, y) {
            var offset = plot.getPlaceholder().offset();
            var plotOffset = plot.getPlotOffset();

            return { x: x - offset.left - plotOffset.left, y: y - offset.top - plotOffset.top };
        }


        /**
         * Called when the user begins to drag the selection.
         * If the mouse is close to the border, the selection will be resized
         * instead.
         */
        function onDragStart(ev) {
            backupSel(); // Backup the current selection. May need to restore it later.
            var point = toCanvas(ev.pageX, ev.pageY);
            if (selection.resizeMode) {
                // The actual resizing may begin
                selection.selecting = true;
            } else if (selection.draggable) {
                // Start dragging.
                selection.selecting = true;
                dragFrom = point;
            } else if (selection.selecting === false) {
                // Start drawing the selection.
                setSelectionPos(fst, point);
                setSelectionPos(snd, point);
                selection.selecting = true;
                selection.show = true;
            } else {
                throw new Error("Inconsistent state: selecting=" + selection.selecting + ", show=" + selection.show);
            }
        }


        /**
         * Called while the user is dragging or resizing the selection.
         */
        function onDrag(ev) {
            var point = toCanvas(ev.pageX, ev.pageY);
            // If the selection is being changed...
            if (selection.selecting) {
                if (selection.draggable) {
                    // Do not drag if the mouse is outside the plot
                    if (canDrag(point)) {
                        // If dragging is enabled, move the selection.
                        moveBy(point.x - dragFrom.x, point.y - dragFrom.y);
                        dragFrom = point;
                    }
                } else if (selection.resizeMode) {
                    resize(selection.resizeMode, point);
                } else if (selection.show) {
                    // If creating the selection for the first time, record the current point.
                    setSelectionPos(snd, point);
                } else {
                    throw new Error("Inconsistent state during drag");
                }

                if (selectionIsSane()) {
                    plot.getPlaceholder().trigger("plotselecting", [getSelection()]);
                    plot.triggerRedrawOverlay();
                }
            } else {
                throw new Error("Invalid state: the user is dragging, but selection.selecting is " + selection.selecting);
            }
        }


        /**
         * Called when the user stops dragging or resizing the selection.
         */

        var cancelClickEvent = false;

        function onDragEnd(ev) {
            // The selection has been defined. Update the state.
            selection.selecting = false;
            selection.resizeMode = 0;

            if (selectionIsSane()) {
                selection.show = true;
                triggerSelectedEvent();
                plot.triggerRedrawOverlay();
            } else {
                clearSelection();
            }

            cancelClickEvent = true;
        }


        function onClick(ev) {
            if (cancelClickEvent) {
                cancelClickEvent = false;
                return;
            }

            var point = toCanvas(ev.pageX, ev.pageY);
            if (!isInsideSelection(point)) {
                plot.clearSelection();
            }
        }



        function onMouseMove(ev) {
            var point = toCanvas(ev.pageX, ev.pageY);

            // If the user is just moving the mouse around, update the cursor
            // as needed.
            if (!selection.selecting) {
                updateCursor(point);
            }

            cursorPos = point;
        }


        /**
         * Updates the cursor.
         * Must be called only when the selection is not being changed (i.e.,
         * when selectiion.selecting is false).
         *
         * @param pos: The cursor position
         */
        function updateCursor(pos) {
            if (selection.selecting) {
                throw new Error("Can't update the cursor while selecting the area");
            }

            var cursor = 'default';
            selection.draggable = false;
            selection.resizeMode = 0;
            var border = borderUnder(pos);
            if (border > 0) {
                if (resizeAllowed(border)) {
                    var cursors = [null, 'w-resize', 'e-resize', 'n-resize', 's-resize', 'nw-resize', 'ne-resize', 'sw-resize', 'se-resize'];
                    // Check if resize in this border is allowed for the selection mode
                    cursor = cursors[border];
                    selection.resizeMode = border;
                }
            } else if (isInsideSelection(pos)) {
                cursor = 'move';
                selection.draggable = true;
            }

            // As of 16/03/2010, the cursor does not change in Chrome if the
            // mouse didn't move. It works in FF though.
            $('body').css('cursor', cursor);
        }


        /**
         * Find out which border or corner is below the given point.
         *
         * 1. Left border      5. Left-top corner
         * 2. Right border     6. Right-top corner
         * 3. Top border       7. Left-bottom corner
         * 4. Bottom border    8. Right-bottom corner
         *
         * 0. No border or corner below that point.
         */
        function borderUnder(point) {
            var xmin = Math.min(fst.x, snd.x);
            var xmax = Math.max(fst.x, snd.x);
            var ymin = Math.min(fst.y, snd.y);
            var ymax = Math.max(fst.y, snd.y);
            var thres = 7;

            // Corners first
            if (Math.abs(point.x - xmin) < thres && Math.abs(point.y - ymin) < thres) {
                return 5;
            } else if (Math.abs(point.x - xmax) < thres && Math.abs(point.y - ymin) < thres) {
                return 6;
            } else if (Math.abs(point.x - xmin) < thres && Math.abs(point.y - ymax) < thres) {
                return 7;
            } else if (Math.abs(point.x - xmax) < thres && Math.abs(point.y - ymax) < thres) {
                return 8;
            } else if (Math.abs(point.x - xmin) < thres && point.y > ymin && point.y < ymax) {
                return 1;
            } else if (Math.abs(point.x - xmax) < thres && point.y > ymin && point.y < ymax) {
                return 2;
            } else if (Math.abs(point.y - ymin) < thres && point.x > xmin && point.x < xmax) {
                return 3;
            } else if (Math.abs(point.y - ymax) < thres && point.x > xmin && point.x < xmax) {
                return 4;
            }

            return 0;
        }

        /**
         * Checks the selection mode to verify if resizing is allowed under the
         * given border.
         */
        function resizeAllowed(border) {
            var mode = plot.getOptions().selection.mode;
            if (mode === "y") {
                return border === 3 || border === 4;
            } else if (mode === "x") {
                return border === 1 || border === 2;
            }

            return true;
        }


        /**
         * Is the given point inside the selection?
         */
        function isInsideSelection(point) {
            return isInsideRect(point, { left: Math.min(fst.x, snd.x),
                                         right: Math.max(fst.x, snd.x),
                                         top: Math.min(fst.y, snd.y),
                                         bottom: Math.max(fst.y, snd.y) });
        }


        /**
         * Is the point inside the rectangle?
         */
        function isInsideRect(point, rect) {
            var xmin = rect.left;
            var xmax = rect.right;
            var ymin = rect.top;
            var ymax = rect.bottom;

            return point.x > xmin && point.x < xmax && point.y > ymin && point.y < ymax;
        }


        /**
         * Can dragging occur when the mouse is at the given point?
         */
        function canDrag(point) {
            var mode = plot.getOptions().selection.mode;
            var left = 0,
                right = plot.width(),
                top = 0,
                bottom = plot.height();

            // If the selection mode is x, allow the mouse to go above or below
            // the plot. Likewise for y.
            if (mode === "x") {
                top = Number.NEGATIVE_INFINITY;
                bottom = Number.POSITIVE_INFINITY;
            } else if (mode === "y") {
                left = Number.NEGATIVE_INFINITY;
                right = Number.POSITIVE_INFINITY;
            }

            return isInsideRect(point, { left: left, right: right, top: top, bottom: bottom });
        }


        function clamp(min, value, max) {
            return value < min? min: (value > max? max: value);
        }


        function leftmost() {
            return fst.x < snd.x ? fst : snd;
        }


        function rightmost() {
            return fst.x < snd.x ? snd : fst;
        }


        function topmost() {
            return fst.y < snd.y ? fst : snd;
        }


        function bottommost() {
            return fst.y < snd.y ? snd : fst;
        }


        /**
         * Moves the selection to the left by dx and to the bottom by dy,
         * according to the current selection mode.
         */
        function moveBy(dx, dy) {
            var left = leftmost();
            var right = rightmost();
            var top = topmost();
            var bottom = bottommost();
            var o = plot.getOptions();

            // The selection can't go past the plot.
            if (left.x + dx < 0) {
                dx = -left.x;
            } else if (right.x + dx > plot.width()) {
                dx = plot.width() - right.x;
            }

            if (top.y + dy < 0) {
                dy = -top.y;
            } else if (bottom.y + dy > plot.height()) {
                dy = plot.height() - bottom.y;
            }


            if (o.selection.mode.indexOf("x") != -1) {
                fst.x += dx;
                snd.x += dx;
            }

            if (o.selection.mode.indexOf("y") != -1) {
                fst.y += dy;
                snd.y += dy;
            }
        }


        /**
         * Moves the side or corner being resized to the given position.
         * FIXME: Kinda ugly.
         */
        function resize(mode, pos) {
            // If resizing, update the appropriate side.
            var left = leftmost();
            var right = rightmost();
            var top = topmost();
            var bottom = bottommost();

            // If I have to modify left's x coordinate...
            if (mode === 1 || mode === 5 || mode === 7) {
                // Make sure the result width is greater than minSize.
                if (pos.x + minSize > right.x) {
                    left.x = right.x - minSize;
                } else if (pos.x < 0) {
                    left.x = 0;
                } else {
                    left.x = pos.x;
                }
            } else if (mode === 2 || mode === 6 || mode === 8) {
                // Make sure the result width is greater than minSize.
                if (pos.x - minSize < left.x) {
                    right.x = left.x + minSize;
                } else if (pos.x > plot.width()) {
                    right.x = plot.width();
                } else {
                    right.x = pos.x;
                }
            }

            if (mode === 3 || mode === 5 || mode === 6) {
                // Make sure the result height is greater than minSize.
                if (pos.y + minSize > bottom.y) {
                    top.y = bottom.y - minSize;
                } else if (pos.y < 0) {
                    top.y = 0;
                } else {
                    top.y = pos.y;
                }
            } else if (mode === 4 || mode === 7 || mode === 8) {
                // Make sure the result height is greater than minSize.
                if (pos.y - minSize < top.y) {
                    bottom.y = top.y + minSize;
                } else if (pos.y > plot.height()) {
                    bottom.y = plot.height();
                } else {
                    bottom.y = pos.y;
                }
            }
        }


        /**
         * Backups the current selection.
         */
        function backupSel() {
            if (plot.hasSelection()) {
                prevSel = getSelection();
            }
        }


        /**
         * Re-applies the previous selection.
         */
        function restoreSel() {
            if (prevSel) {
                setSelection(prevSel);
            }
        }


        /**
         * Sets the given selection point to the specified coordinates.
         * Respects the selection mode.
         */
        function setSelectionPos(point, pos) {
            var o = plot.getOptions();
            point.x = clamp(0, pos.x, plot.width());
            point.y = clamp(0, pos.y, plot.height());

            if (o.selection.mode === "y")
                point.x = point === fst ? 0 : plot.width();

            if (o.selection.mode === "x")
                point.y = point === fst ? 0 : plot.height();
        }


        /**
         * Checks if the current selection is not too small.
         */
        function selectionIsSane() {
            return Math.abs(selection.second.x - selection.first.x) >= minSize &&
                Math.abs(selection.second.y - selection.first.y) >= minSize;
        }


        plot.hooks.bindEvents.push(function(plot, eventHolder) {
            var o = plot.getOptions();
            if (o.selection.mode !== null) {
                eventHolder.bind('click', onClick);
                eventHolder.bind('dragstart', onDragStart);
                eventHolder.bind('drag', onDrag);
                eventHolder.bind('dragend', onDragEnd);
                eventHolder.mousemove(onMouseMove);
            }
        });


        /**
         * Update the selection if plot width or height changes.
         */
        plot.hooks.draw.push(function (plot, ctx) {
            var width = plot.width();
            var height = plot.height();
            var options = plot.getOptions();
            var changed = false;
            if (options.selection.mode === "y") {
                if (snd.x !== width) {
                    snd.x = width;
                    changed = true;
                }
            }

            if (options.selection.mode === "x") {
                if (snd.y !== height) {
                    snd.y = height;
                    changed = true;
                }
            }

            plot.triggerRedrawOverlay();
        });


        /**
         * Draw the selection.
         */
        plot.hooks.drawOverlay.push(function (plot, ctx) {
            if (selection.show) {
                var plotOffset = plot.getPlotOffset();
                var o = plot.getOptions();

                ctx.save();
                ctx.translate(plotOffset.left, plotOffset.top);

                var c = $.color.parse(o.selection.color);

                ctx.strokeStyle = c.scale('a', 0.8).toString();
                ctx.lineWidth = 1;
                ctx.lineJoin = "round";
                ctx.fillStyle = c.scale('a', 0.4).toString();

                var x = Math.min(selection.first.x, selection.second.x),
                    y = Math.min(selection.first.y, selection.second.y),
                    w = Math.abs(selection.second.x - selection.first.x),
                    h = Math.abs(selection.second.y - selection.first.y);

                ctx.fillRect(x, y, w, h);
                ctx.strokeRect(x, y, w, h);
                ctx.restore();

                // If the selection moved, the cursor may need to be updated.
                if (!selection.selecting) {
                    updateCursor(cursorPos);
                }
            }
        });
    }

    $.plot.plugins.push({
        init: init,
        options: {
            selection: {
                mode: null, // one of null, "x", "y" or "xy"
                color: "#e8cfac"
            }
        },
        name: 'selection',
        version: '1.0'
    });
})(jQuery);

