/*
 * positionBy 1.0.7 (2008-01-29)
 *
 * Copyright (c) 2006,2007 Jonathan Sharp (http://jdsharp.us)
 * Dual licensed under the MIT (MIT-LICENSE.txt)
 * and GPL (GPL-LICENSE.txt) licenses.
 *
 * http://jdsharp.us/
 *
 * Built upon jQuery 1.2.2 (http://jquery.com)
 * This also requires the jQuery dimensions plugin
 */
(function($){
    /**
     * This function centers an absolutely positioned element
     */
    /*
    $.fn.positionCenter = function(offsetLeft, offsetTop) {
        var offsetLeft  = offsetLeft || 1;
        var offsetTop   = offsetTop || 1;

        var ww = $(window).width();
        var wh = $(window).height();
        var sl = $(window).scrollLeft();
        var st = $(window).scrollTop();

        return this.each(function() {
            var $t = $(this);
            
            // If we are not visible we have to display our element (with a negative position offscreen)

            var left = Math.round( ( ww - $t.outerWidth() ) / 2 );
            if ( left < 0 ) {
                left = 0;
            } else {
                left *= offsetLeft;
            }
            left += sl;
            var top  = Math.round( ( wh - $t.outerHeight() ) / 2 );
            if ( top < 0 ) {
                top = 0;
            } else {
                top *= offsetTop;
            }
            top += st;

            $(this).parents().each(function() {
                var $this = $(this);
                if ( $this.css('position') != 'static' ) {
                    var o = $this.offset();
                    left += -o.left;
                    top  += -o.top;
                    return false;
                }
            });

            $t.css({left: left, top: top});
        });
    };
    */
    
    // Our range object is used in calculating positions
    var Range = function(x1, y1, x2, y2) {
        this.x1 = x1;   this.x2 = x2;
        this.y1 = y1;   this.y2 = y2;
    };
    Range.prototype.contains = function(range) {
        return  (this.x1 <= range.x1 && range.x2 <= this.x2) 
                && 
                (this.y1 <= range.y1 && range.y2 <= this.y2);
    };
    Range.prototype.transform = function(x, y) {
        return new Range(this.x1 + x, this.y1 + y, this.x2 + x, this.y2 + y);
    };

    $.fn.positionBy = function(args) {
        var date1 = new Date();
        if ( this.length === 0 ) {
            return this;
        }
        
        args = $.extend({   // The target element to position us relative to
                                target:     null,
                                // The target's corner, possible values 0-3
                                targetPos:  null,
                                // The element's corner, possible values 0-3
                                elementPos: null,
                                
                                // A raw x,y coordinate
                                x:          null,
                                y:          null,

                                // Pass in an array of positions that are valid 0-15
                                positions:  null,

                                // Add the final position class to the element (eg. positionBy0 through positionBy3, positionBy15)
                                addClass:   false,
                                
                                // Force our element to be at the location we specified (don't try to auto position it)
                                force:      false,
                                
                                // The element that we will make sure our element doesn't go outside of
                                container:  window,

                                // Should the element be hidden after positioning?
                                hideAfterPosition: false
                            }, args);

        if ( args.x !== null ) {
            var tLeft   = args.x;
            var tTop    = args.y;
            var tWidth  = 0;
            var tHeight = 0;
            
        // Position in relation to an element
        } else {
            var $target = $( $( args.target )[0] );
            tWidth  = $target.outerWidth();
            tHeight = $target.outerHeight();
            var tOffset = $target.offset();
            tLeft   = tOffset.left;
            tTop    = tOffset.top;
        }

        // Our target right, bottom coord
        var tRight  = tLeft + tWidth;
        var tBottom = tTop + tHeight;

        return this.each(function() {
            var $element = $( this );

            // Position our element in the top left so we can grab its width without triggering scrollbars
            if ( !$element.is(':visible') ) {
                $element.css({  left:       -3000, 
                                top:        -3000
                                })
                                .show();
            }

            var eWidth  = $element.outerWidth();
            var eHeight = $element.outerHeight();
    
            // Holds x1,y1,x2,y2 coordinates for a position in relation to our target element
            var position = [];
            // Holds a list of alternate positions to try if this one is not in the browser viewport
            var next     = [];
    
            // Our Positions via ASCII ART
            /*
             8   9       10   11
               +------------+
             7 | 15      12 | 0
               |            |
             6 | 14      13 | 1
               +------------+ 
             5   4        3   2
    
             */

            position[0] = new Range(tRight,             tTop,               tRight + eWidth,    tTop + eHeight);
            next[0]     = [1,7,4];
        
            position[1] = new Range(tRight,             tBottom - eHeight,  tRight + eWidth,    tBottom);
            next[1]     = [0,6,4];
        
            position[2] = new Range(tRight,             tBottom,            tRight + eWidth,    tBottom + eHeight);
            next[2]     = [1,3,10];
        
            position[3] = new Range(tRight - eWidth,    tBottom,            tRight,             tBottom + eHeight);
            next[3]     = [1,6,10];
            
            position[4] = new Range(tLeft,              tBottom,            tLeft + eWidth,     tBottom + eHeight);
            next[4]     = [1,6,9];
        
            position[5] = new Range(tLeft - eWidth,     tBottom,            tLeft,              tBottom + eHeight);
            next[5]     = [6,4,9];
        
            position[6] = new Range(tLeft - eWidth,     tBottom - eHeight,  tLeft,              tBottom);
            next[6]     = [7,1,4];
            
            position[7] = new Range(tLeft - eWidth,     tTop,               tLeft,              tTop + eHeight);
            next[7]     = [6,0,4];
            
            position[8] = new Range(tLeft - eWidth,     tTop - eHeight,     tLeft,              tTop);
            next[8]     = [7,9,4];
            
            position[9] = new Range(tLeft,              tTop - eHeight,     tLeft + eWidth,     tTop);
            next[9]     = [0,7,4];
            
            position[10]= new Range(tRight - eWidth,    tTop - eHeight,     tRight,             tTop);
            next[10]    = [0,7,3];
        
            position[11]= new Range(tRight,             tTop - eHeight,     tRight + eWidth,    tTop);
            next[11]    = [0,10,3];
            
            position[12]= new Range(tRight - eWidth,    tTop,               tRight,             tTop + eHeight);
            next[12]    = [13,7,10];
            
            position[13]= new Range(tRight - eWidth,    tBottom - eHeight,  tRight,             tBottom);
            next[13]    = [12,6,3];
            
            position[14]= new Range(tLeft,              tBottom - eHeight,  tLeft + eWidth,     tBottom);
            next[14]    = [15,1,4];
            
            position[15]= new Range(tLeft,              tTop,               tLeft + eWidth,     tTop + eHeight);
            next[15]    = [14,0,9];
    
            if ( args.positions !== null ) {
                var pos = args.positions[0];
            } else if ( args.targetPos !== null && args.elementPos !== null ) {
                pos = [];
                pos[0] = [];
                pos[0][0] = 15;
                pos[0][1] = 7;
                pos[0][2] = 8;
                pos[0][3] = 9;
                pos[1] = [];
                pos[1][0] = 0;
                pos[1][1] = 12;
                pos[1][2] = 10;
                pos[1][3] = 11;
                pos[2] = [];
                pos[2][0] = 2;
                pos[2][1] = 3;
                pos[2][2] = 13;
                pos[2][3] = 1;
                pos[3] = [];
                pos[3][0] = 4;
                pos[3][1] = 5;
                pos[3][2] = 6;
                pos[3][3] = 14;

                pos = pos[args.targetPos][args.elementPos];
            }
            var ePos = position[pos];
            var fPos = pos;

            if ( !args.force ) {
                // TODO: Do the args.container
                // window width & scroll offset
                $window = $( window );
                var sx = $window.scrollLeft();
                var sy = $window.scrollTop();
                
                // TODO: Look at innerWidth & innerHeight
                var container = new Range( sx, sy, sx + $window.width(), sy + $window.height() );
    
                // If we are outside of our viewport, see if we are outside vertically or horizontally and push onto the stack
                var stack;
                if ( args.positions ) {
                    stack = args.positions;
                } else {
                    stack = [pos];
                }
                var test = [];      // Keeps track of our positions we already tried
                
                while ( stack.length > 0 ) {
                    var p = stack.shift();
                    if ( test[p] ) {
                        continue;
                    }
                    test[p] = true;
    
                    // If our current position is not within the viewport (eg. window) 
                    // add the next suggested position
                    if ( !container.contains(position[p]) ) {
                        if ( args.positions === null ) {
                            stack = jQuery.merge( stack, next[p] );
                        }
                    } else {
                        ePos = position[p];
                        break;
                    }
                }
            }

            // + TODO: Determine if we are going to use absolute left, top, bottom, right 
            // positions relative to our target
        
            // Take into account any absolute or fixed positioning
            // to 'normalize' our coordinates
            $element.parents().each(function() {
                var $this = $(this);
                if ( $this.css('position') != 'static' ) {
                    var abs = $this.offset();
                    ePos = ePos.transform( -abs.left, -abs.top );
                    return false;
                }
            });
        
            // Finally position our element
            var css = { left: ePos.x1, top: ePos.y1 };
            if ( args.hideAfterPosition ) {
                css.display = 'none';
            }
            $element.css( css );

            if ( args.addClass ) {
                $element.removeClass( 'positionBy0 positionBy1 positionBy2 positionBy3 positionBy4 positionBy5 '
                                    + 'positionBy6 positionBy7 positionBy8 positionBy9 positionBy10 positionBy11 '
                                    + 'positionBy12 positionBy13 positionBy14 positionBy15')
                        .addClass('positionBy' + p);
            }
        });
    };
})(jQuery);
