/*
Script: Croppr.js
  An image cropping utility that's sexy ass hell.

Author:
  Chris Van Pelt, <http://vandev.com>

License:
  MIT-style license.
*/

/*
Class: Croppr
  The kick ass class for image cropping

Arguments:
  src - the src of the image you're wanting to crop
  
Options:
  crop - an array with two integers representing the size of the crop; default - [80,80]
  url - a function to be exectued when the 'Crop' button is pushed (accepts a hash of values); default - change the window location to '?__params__'
  scale - the initial scale of the image (between 0 and 1); default - the biggest it can get and still fit in the window
  initial - the initial distance from the top left of the image to the top left of the crop box
  background - a hex string specifying the color of the initial fade; default - '#000'

Example:
  (start code)
  new Croppr('/croppr/images/'+id, {crop: [100,100], url: function(v){window.location = 'crop/'+id+'/'+v}, scale: $.5, initial: [100,250]});
  (end)
  
Notes:
  Make sure you're in strict mode aka doctype XHTML Strict with no <?xml declration before it!!! This creates a div with an id of 'croppr', in which is the image being cropped, two link tags with classes 'exit' and 'crop', another div tag which is the mask containing a clone of the main image, and a div with the class of 'track', containing another div with the class of 'handle'... style as you please
*/

var Croppr = new Class ({
  initialize: function(src) {
    var options = Object.extend({
      crop: [80,80],
      url: function(v) {window.location = '?'+v},
      scale: 0,
      initial: false,
	  onExit: Class.empty, //added
      background: '#FFF'
    }, arguments[1] || {});
    
    this.container = new Element('div').setStyles({
      position: 'absolute',
      top: 0,
      left: 0, 
      overflow: 'hidden', 
      width: '100%',
      height: window.getHeight()+'px'
    });
	
    this.options = options;
    this.options.crop = this.options.crop.map(function(i){return i.toInt()});
        
    preloader = new Image();
    preloader.onload=function(){
      this.image = new Element('img').setProperty("src", src);
      this.size = [preloader.width, preloader.height];
      this.original_size = this.size;
	  
      //Check for invalid size
      if(this.size[0] < this.options.crop[0] || this.size[1] < this.options.crop[1]) {
        alert(this.options.crop.join("x")+" is bigger than "+this.size[0]+"x"+this.size[1]+", and that's just not gonna fly.");
        return false;
      }
            
      //Set the initial position or center it...
      if(this.options.initial && this.options.initial != "false") {
        this.options.initial = this.options.initial.map( function(i) {
          return -i.toFloat();
        });
      }
      else 
        this.options.initial = [-this.size[0]/2, -this.size[1]/2];
        
      this.image.setStyles({
        cursor: 'move', 
        position:'absolute',
        visibility:'hidden',
        top: '50%', 
        left: '50%', 
        marginTop: this.options.initial[1]+'px', 
        marginLeft: this.options.initial[0]+'px'
      })
      
      this.create();
      window.addEvent('resize', this.draw.bind(this));
    }.bind(this);
    preloader.src = src;
  },
  //Creates Lightbox, mask, slider, and dragable image
  create: function() {
    this.createTrans();
    this.createCrop();
    this.createControls();
    //calculate the minimum scale ratio for the slider
    this.minimum_size = Math.max(this.options.crop[0] / this.original_size[0] * 100, this.options.crop[1] / this.original_size[1] * 100);
    
    this.drag = new Drag.Base(this.image, {
      limit: {
        x: [-this.size[0] + this.options.crop[0] / 2, -this.options.crop[0] / 2], 
        y: [-this.size[1] + this.options.crop[1] / 2, -this.options.crop[1] / 2]
      },
      modifiers: {
        x: "margin-left", 
        y: "margin-top"
      },
      onDrag: this.draw.bind(this)
    });
    this.slider = new Slider(this.track,this.handle,{
      onChange: this.updateSlide.bind(this),
      steps: 100 - this.minimum_size,
      wheel: true
    }).set(100 - this.minimum_size);
    
    //Automatically scale the image if it doesn't fit in the window and a initial scale was not specified
    if(this.options.scale == 0) 
      this.options.scale = Math.min(1, Math.min(window.getHeight() / this.size[1], window.getWidth() / this.size[0]));
    if(this.options.scale * 100 < this.minimum_size) 
      this.options.scale = this.minimum_size / 100;
      
    //Perform the initial scale and opacity effects
    new Fx.Style(this.image, 'opacity', {duration: 500}).custom(0.0, 0.3);
    //this.image.setOpacity(0.3);
    this.draw();
    new Fx.ImageScale(this, {duration:3000}).custom(100-this.minimum_size, this.options.scale*100-this.minimum_size);
  },
  //Creates the transparent light box...
  createTrans: function() {
    this.trans = this.container.clone(false).setStyles({
      visibility: 'hidden',
      background: this.options.background,
      overflow: 'visible'
    }).injectInside(document.body);
    new Fx.Style(this.trans, 'opacity', {duration: 1000}).custom(0.0, 0.8);
    this.container.setProperty('id', 'croppr').injectInside(document.body);
  },
  //Creates the crop box...
  createCrop: function(){
    this.mask = new Element("div").setStyles({
      border: "1px solid #000",
      overflow: "hidden",
      position: "absolute",
      width: this.options.crop[0]-2+"px",
      height: this.options.crop[1]-2+"px",
      top: "50%",
      left: "50%", 
      marginTop: -this.options.crop[1]/2+"px", 
      marginLeft: -this.options.crop[0]/2+"px" 
    }).injectInside(this.container);
    this.image.injectInside(this.container);
    this.ghost = this.image.clone(false).injectInside(this.mask);
  },
  //Creates the controls...
  createControls: function(){
    new Element('a').addClass('exit').addEvent('mousedown', function(){
      this.options.onExit(); //added
	  this.destroy();
      return false
    }.bind(this)).setHTML("<span>EXIT</span>").injectInside(this.container);
    new Element('a').addClass('crop').addEvent('mousedown', function(){
      this.options.url(this.values());
	  //this.destroy();
      return false
    }.bind(this)).setHTML("<span>CROP</span>").injectInside(this.container);
    this.track = new Element('div').addClass('track').injectInside(this.container);
    this.handle = new Element('div').addClass('handle').setStyle('cursor', 'move').injectInside(this.track);
  },
  //positions mask, transparency, and image after resize
  draw: function() {
    this.ghost.style.cssText = this.image.style.cssText;
    this.ghost.setOpacity(1);
  },
  //resize and recenter the image on the crop
  updateSlide: function(percent) {
    v = (percent + this.minimum_size) / 100;
    this.size = this.original_size.map(function(i){return i * v}.bind(v));
    //Add two for the border...
    this.drag.options.limit = {'x': [-this.size[0] + this.options.crop[0] / 2, -this.options.crop[0] / 2], 'y': [-this.size[1] + this.options.crop[1] / 2, -this.options.crop[1] / 2]};
    
    curmargin = [this.image.getStyle("margin-left").toFloat(), this.image.getStyle("margin-top").toFloat()]
    //width_of_margin_bottom_right_anchor / ratio_of_current_margin_and_with + current_margin
    newmargin = {
      x: (this.image.width - this.size[0]) / (this.image.width / -curmargin[0]) + curmargin[0],
      y: (this.image.height - this.size[1]) / (this.image.height / -curmargin[1]) + curmargin[1]
    };
    
    //Constrain her...
    for (var z in newmargin) {
      if (newmargin[z] > this.drag.options.limit[z][1]) 
        newmargin[z] = this.drag.options.limit[z][1];
      else if (newmargin[z] < this.drag.options.limit[z][0]) 
        newmargin[z] = this.drag.options.limit[z][0];
    }
    
    this.image.setStyles({
      marginTop: newmargin['y']+'px', 
      marginLeft: newmargin['x']+'px', 
      width: this.size[0]+'px', 
      height: this.size[1]+'px'
    });
    
    this.draw();
  },
  destroy: function() {
    this.container.remove();
    new Fx.Style(this.trans, 'opacity', {duration: 1000}).custom(0.8, 0);
    setTimeout(function(){
      this.trans.remove()
    }.bind(this), 1200);
  },
  cropOffset: function() {
    return [-(this.image.getPosition().x - this.mask.getPosition().x), -(this.image.getPosition().y - this.mask.getPosition().y)]
  },
  //returns a javascript hash of values for the crop
  values: function() {
    return {w:this.options.crop[0],h:this.options.crop[1],s:(this.slider.step + this.minimum_size) / 100,x:this.cropOffset()[0],y:this.cropOffset()[1]};
  }
});

Fx.ImageScale = new Class({
	
	Extends: Fx,
	
	initialize: function(el, options) {
      this.el = el;
      this.parent(options);
      this.now = 100;
  },
  increase: function() {
    this.el.slider.set(this.now);
  }
});
