/**
 * ReMooz - Image Zoombox
 * 
 * 
 * Inspired by so many boxes and zooms
 * 
 * @version      1.0rc1
 * 
 * @license      MIT-style license
 * @author      Harald Kirschner <mail [at] digitarald.de>
 * @copyright   Author
 */
var ReMooz = new Class({

   Implements: [Events, Options],

   options: {
      url: null,
      type: 'image',
      className: null,
      positionToCenter: false,
      dragging: true,
      shadow: true,
      resize: true,
      margin: 20,
      resizeFactor: 0.8,
      resizeLimit: false, // {x: 640, y: 640}
      hideSource: true,
      addClick: true,
      resizeOptions: {},
      resizeOpacity: 1,
      fxsOptions: {},
      generateTitle: null,
      showTitle: null,
      onBuild: $empty,
      onLoad: $empty,
      onOpen: $empty,
      onOpenEnd: $empty,
      onClose: $empty,
      onCloseEnd: $empty
   },

   initialize: function(element, options) {
      this.element = $(element);
      this.setOptions(options);
      this.url = this.options.url || this.element.href || this.element.src;

      this.addEvent('onBlur', function() {
         this.focussed = false;
         this.box.removeClass('remo-box-focus').setStyle('z-index', ReMooz.options.zIndex);
      }.bind(this));
      this.addEvent('onFocus', function() {
         this.focussed = true;
         this.box.addClass('remo-box-focus').setStyle('z-index', ReMooz.options.zIndexFocus);
      }.bind(this));

      this.element.addClass('remo-zoom-in').addEvent('trash', this.destroy.bind(this));

      this.clickEvent = function(e) {
         this.open.delay(1, this);
         return false;
      }.bind(this);
      if (this.options.addClick) this.bindToElement();
   },

   destroy: function(unload) {
      if (this.box && !unload) this.box.destroy();
      this.box = this.boxFx = this.content = null;
      return null;
   },

   bindToElement: function(el) {
      ($(el) || this.element).addEvent('click', this.clickEvent);
      return this;
   },

   getSourceCoordinates: function() {
      var coords = this.element.getElement('img').getCoordinates();
      delete coords.right;
      delete coords.bottom;
      return coords;
   },

   open: function(e) {
      if (this.opened) return (e) ? this.close() : this;
      this.opened = true;
      if (!this.box) this.build();
      this.coords = this.getSourceCoordinates();
      this.coords.opacity = 0.7;
      this.boxFx.set(this.coords);
      this.box.setStyle('display', '').addClass('remo-loading');
      this.boxDrag = this.boxDrag || new Drag.Move(this.box, { // inits here because of safari
         'snap': 15,
         'onStart': function() {
            if (!this.focussed && !this.loading) {
               ReMooz.focus(this);
               this.focusEvent = true;
            }
         }.bind(this),
         'onSnap': function() {
            this.dragging = true;
            this.box.addClass('remo-box-dragging');
         }.bind(this),
         'onComplete': function() {
            if (!this.dragging && !this.focusEvent) this.close();
            this.dragging = this.focusEvent = false;
            this.box.removeClass('remo-box-dragging');
         }.bind(this)
      }).detach();
      this.fireEvent('onLoad');
      this.loadImage();
      return this;
   },

   openEnd: function() {
      ReMooz.open(this);
      this.zoomed = true;
      if (this.options.dragging) this.boxDrag.attach();
      this.fxs.start(0, 1);
      this.fireEvent('onOpenEnd');
   },

   close: function() {
      if (!this.opened) return this;
      this.fireEvent('onClose');
      this.opened = this.zoomed = false;
      ReMooz.close(this);
      if (this.loading) {
         this.box.setStyle('display', 'none');
         return this;
      }
      this.boxDrag.detach();
      this.fxs.set(0);
      if (this.boxFx.timer) this.boxFx.clearChain();
      var vars = this.getSourceCoordinates();
      if (this.options.resizeOpacity != 1) vars.opacity = this.options.resizeOpacity;
      this.boxFx.start(vars).chain(this.closeEnd.bind(this));
      return this;
   },

   closeEnd: function() {
      this.element.setStyle('visibility', 'visible');
      this.box.setStyle('display', 'none');
      this.fireEvent('onCloseEnd');
   },

   loadImage: function() {
      this.loading = true;
      var loader = new Image();
      loader.onload = loader.onabort = loader.onerror = function(fast) {
         this.loading = loader.onload = loader.onabort = loader.onerror = null;
         if (!loader.width || !this.opened) {
            this.close();
            return;
         }
         var to = {x: loader.width, y: loader.height};
         if (!this.image)
         {
             this.image = (Client.Engine.webkit419) ? new Element('img', {'src': loader.src}) : $(loader);
             this.image.addClass('remo-img').inject(this.content);
         } else loader = null;
         this.openImage.create({
            'delay': (loader && fast !== true) ? 1 : null,
            'arguments': [to],
            'bind': this
         })();
      }.bind(this);
      loader.src = this.url;
      if (loader && loader.complete && loader.onload) loader.onload(true);
   },

   openImage: function(size) {
      if (this.options.hideSource) this.element.setStyle('visibility', 'hidden');
      this[(this.options.resize) ? 'zoomRelativeTo' : 'zoomTo'](size);
   },

   zoomRelativeTo: function(to) {
      var max = this.options.resizeLimit || {
         x: Client.getWidth() * this.options.resizeFactor,
         y: Client.getHeight() * this.options.resizeFactor
      };
      for (var i = 2; i--;) {
         if (to.x > max.x) {
            to.y *= max.x / to.x;
            to.x = max.x;
         } else if (to.y > max.y) {
            to.x *= max.y / to.y;
            to.y = max.y;
         }
      }
      return this.zoomTo({'x': to.x.toInt(), 'y': to.y.toInt()});
   },

   zoomTo: function(to) {
      var box = window.getSize();
      var pos = (!this.options.positionToCenter) ? {
         x: (this.coords.left + (this.coords.width / 2) - to.x / 2).toInt()
            .limit(box.scroll.x + this.options.margin, box.scroll.x + box.size.x - this.options.margin - to.x),
         y: (this.coords.top + (this.coords.height / 2) - to.y / 2).toInt()
            .limit(box.scroll.y + this.options.margin, box.scroll.y + box.size.y - this.options.margin - to.y)
      } :  {
         x: box.scroll.x + ((box.size.x - to.x) / 2).toInt(),
         y: box.scroll.y + ((box.size.y - to.y) / 2).toInt()
      };
      this.box.removeClass('remo-loading');
      this.fireEvent('onOpen');
      var vars = {'left': pos.x, 'top': pos.y, 'width': to.x, 'height': to.y};
      if (this.options.resizeOpacity != 1) vars.opacity = [this.options.resizeOpacity, 1];
      else this.box.setStyle('opacity', 1);
      this.boxFx.start(vars).chain(this.openEnd.bind(this));
   },

   generateTitle: function() {
      var title = this.options.title || this.element.getProperty('title');
      if (!title) return false;
      title = title.split(' :: ');
      var ret = [new Element('h4').setHTML(title[0])];
      if (title[1]) ret.push(new Element('p').setHTML(title[1]));
      return ret;
   },

   build: function() {
      this.box = new Element('div', {
         'class': 'remo-box',
         styles: {
            display: 'none',
            zIndex: ReMooz.options.zIndex
         },
         events: {
            click: function() {
               if (this.zoomed) return true;
               this.close();
               return false;
            }.bind(this)
         }
      });
      if (this.options.className) this.box.addClass(this.options.className);
      this.boxFx = new Fx.Styles(this.box, $merge({
         duration: 400,
         unit: 'px',
         transition: Fx.Transitions.Quart.easeOut,
         wait: false
      }, this.options.resizeOptions));

      if (this.options.shadow) {
         var shadow = new Element('div', {'class': 'remo-bg-wrap'}).inject(this.box);
         ['n', 'ne', 'e', 'se', 's', 'sw', 'w', 'nw'].each(function(dir) {
            new Element('div', {'class': 'remo-bg remo-bg-' + dir}).inject(shadow);
         });
         var shadowFx = new Fx.Style(shadow, 'opacity', {wait: false}).hide();
         this.addEvent('onOpen', shadowFx.start.pass(1, shadowFx))
            .addEvent('onClose', shadowFx.hide.bind(shadowFx));
      }
      this.content = new Element('div', {'class': 'remo-content'}).inject(this.box);

      var btn = new Element('a', {
         'class': 'remo-btn-close',
         events: {'click': this.close.bind(this)}
      }).inject(this.box);
      this.fxs = new Fx.Style(btn, 'opacity', $merge({
         duration: 300,
         wait: false
      }, this.options.fxsOptions));
      var title = (this.options.generateTitle || this.generateTitle).call(this);
      if (title) {
         this.title = new Element('div', {'class': 'remo-title'})
            .inject(new Element('div', {'class': 'remo-title-wrap'}).inject(this.box));
         new Element('div', {'class': 'remo-title-bg'}).setOpacity(0.8).inject(this.title);
         new Element('div', {'class': 'remo-title-txt'})
            [$type(title) == 'string' ? 'setHTML' : 'adopt'](title).inject(this.title);
      }
      this.fxs.element = $$(btn, this.title);
      this.fxs.set(0);
      this.box.inject(document.body);
   }

});

ReMooz.factory = $extend;

ReMooz.factory({

   options: {
      zIndex: 41,
      zIndexFocus: 42,
      query: 'a.remooz',
      optionsField: 'rel',
      classOptions: {}
   },

   initialize: function(elements, options) {
      this.setOptions(options);
      return $$(elements || this.options.query).map(function(el) {
         var obj = el.getProperty(this.options.optionsField);
         if (obj && (obj = Json.decode(obj, true))) obj = $merge(obj, this.options.classOptions);
         return (el.$attributes.remooz = new ReMooz(el, obj || this.options.classOptions));
      }, this);
   },

   stack: [],

   open: function(obj) {
      this.focus(obj);
   },

   close: function(obj) {
      var last = this.stack.length - 1;
      if (this.stack.indexOf(obj) == last) this.focus(this.stack[last - 1]);
      this.stack.remove(obj);
   },

   focus: function(obj) {
      var last = this.stack.getLast();
      if (!obj || last == obj) return;
      if (last) last.fireEvent('onBlur', [last], 10);
      obj.fireEvent('onFocus', [obj], 10);
      this.stack.remove(obj).push(obj);
   }

});

ReMooz.factory(new Options);