/*
 * 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;
    }
    
    var 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] );
      var tWidth  = $target.outerWidth();
      var tHeight  = $target.outerHeight();
      var tOffset  = $target.offset();
      var tLeft  = tOffset.left;
      var 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 ) {
        var 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;

        var 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);

