(function($) {

$.fn.zoom = function(command, options) {
  var zoomer = $(this).data('zoomer');
  
  if (!zoomer) {
    zoomer = new ImageZoomer(this);
    $(zoomer.wrapper).data('zoomer', zoomer);
  }
  
  if (command == 'in') { zoomer.zoomIn(); }
  else if (command == 'out') { zoomer.zoomOut(options); }
  else if (command == 'src') { zoomer.src(options); }
  
  return zoomer;
};

var ImageZoomer = function(img) {
  var self = this;
  
  this.img = img.wrap('<div></div>');
  this.wrapper = this.img.parent();
  this.wrapper.attr('id', img.attr('id'));
  this.img.removeAttr('id');
  
  var centerX = this.wrapper.width() / 2;
  var centerY = this.wrapper.height() / 2;
  
  this.links = {
    zoomIn: $('<a href="#" class="zoom-in">Zoom In</a>').appendTo(this.wrapper).click(function() { self.zoomIn(centerX, centerY); }),
    zoomOut: $('<a href="#" class="zoom-out">Zoom Out</a>').appendTo(this.wrapper).click(function() { self.zoomOut(); }).hide()
  };
  
  this.zoomed = false;
  this.panning = false;
  
  this.wrapper.addClass('out');
  
  this.img.click(function(e) {
    if (!self.zoomed) {
      var offset = self.wrapper.offset();
      self.zoomIn(e.pageX - offset.left, e.pageY - offset.top);
    }
  });
  this.img.dblclick(function() {
    if (self.zoomed) self.zoomOut();
  });
  
  this.img.mousedown(function(e) {
    if (self.zoomed) {
      self.panning = true;
      e.preventDefault();
      
      self.dragX = e.pageX;
      self.dragY = e.pageY;
    }
  });
  
  $(document).mousemove(function(e) { self.pan(e) });
  $(document).mouseup(function(e) { self.panning = false; });
};
ImageZoomer.prototype = {
  src: function(url) {
    this.img.hide();
    this.img.attr('src', url);
    this.img.one('load', function() { $(this).show(); });
  },
  
  zoomIn: function(centerX, centerY) {
    this.zoomed = true;
    this.wrapper.removeClass('out').addClass('in');
    
    this.links.zoomIn.hide();
    this.links.zoomOut.show();
    
    var maxX = this.wrapper.width() - 1000;
    var maxY = this.wrapper.height() - 1000;
    
    var x = maxX * centerX / this.wrapper.width();
    var y = maxY * centerY / this.wrapper.height();
    
    this.img.animate({
      width: 1000,
      height: 1000,
      top: y,
      left: x
    });
  },
  
  zoomOut: function(options) {
    this.zoomed = false;
    this.panning = false;
    this.wrapper.removeClass('in').addClass('out');
    
    this.links.zoomOut.hide();
    this.links.zoomIn.show();
    
    var method = (options && options.animate == false) ? 'css' : 'animate';
    this.img[method]({
      width: '100%',
      height: '100%',
      top: 0,
      left: 0
    });
  },
  
  pan: function(e) {
    if (this.panning) {
      var deltaX = e.pageX - this.dragX;
      var deltaY = e.pageY - this.dragY;
      
      this.dragX = e.pageX;
      this.dragY = e.pageY;
      
      var offset = this.img.position();
      
      var newX = bound(offset.left + deltaX, this.wrapper.width() - 1000, 0);
      var newY = bound(offset.top + deltaY, this.wrapper.height() - 1000, 0);
      
      this.img.css({
        top: newY,
        left: newX
      });
    }
  }
};

function bound(value, min, max) {
  if (value < min) return min;
  if (value > max) return max;
  return value;
}

})(jQuery);
