/* The MIT License

Copyright (c) 2008 Stanislav Müller
http://lifedraft.de

Version: 1.1

*/

var DhoniShow = function(element, options) {
  if(this.elementsSet(element)){
    this.current_index = 0;
    this.queue = new this.queue();
    this.dom = new this.dom(element, this);
    this.options = jQuery.extend(jQuery.extend(this.defaultOptions(), options), new this.options(element));
    
    this.queue.invokeModulesQueue(this, this.options);
  }
};

DhoniShow.prototype = {
  defaultOptions: function(){
    return {
      preloader : true,
      duration : 10,
      center : { elements: true },
      effect : 'appear'
    };
  },
  
  animating: function() {
    return this.dom.element.find("*:animated").length;
  },

  elementsSet: function(element) {
    if ( jQuery( element ).children().length > 0 ) return true;

    jQuery( element ).append("<p>Plese read instructions about using DhoniShow on "+
      "<a href='http://' style='color: #fff;' title=' '> </a></p>").find("p").addClass("error");
    return false;
  }

};

DhoniShow.prototype.queue = function(){
  this.queues = {};
  this.invoked = [];
};

DhoniShow.prototype.queue.prototype = {
  moduleQueue: [],

  register: function(type, scope, func /*, nArgs */){
    this.queues[type] = this.queues[type] || [];
    var args = []; for (var i = 3; i < arguments.length; i++) args.push(arguments[i]);
    this.queues[type].push({scope: scope, func: func, args: args});
    return func;
  },

  invokeAll: function(type /*, nArgs */) {
    if(this.invoked.length != this.moduleQueue.length) { // Cache invokes until all modules have ran
      this.setWaiter(jQuery.makeArray(arguments));
      return;
    }

    for (var i=0; i < this.queues[type].length; i++) {
      if(arguments.length > 1){
        var args = jQuery.makeArray(arguments);
        args.shift();
        jQuery.extend(this.queues[type][i].args, args);
      }
      this.queues[type][i].func.apply(this.queues[type][i].scope, this.queues[type][i].args);
    }
  },

  invokeModulesQueue: function(_this, options){
    var optionsQueue = this.moduleQueue;    
    for (var i=0; i < optionsQueue.length; i++) {
      _this[optionsQueue[i]] = new DhoniShow.fn[optionsQueue[i]](options[optionsQueue[i]], _this);
      this.invoked.push(optionsQueue[i]);
    }
  },

  setWaiter: function(args){
    var _this = this;
    return setTimeout(function(){
      if(_this.invoked.length != _this.moduleQueue.length) {
        setTimeout(arguments.callee, 100);
      } else {
        _this.invokeAll.apply(_this, args);
      }
    }, 100);
  }
};

// ###########################################################################

DhoniShow.prototype.options = function(element) {

  var names = element.className.match(/((\w*)-(\w*)|\w*)_(\w*-\w*-\w*|\w*-\w*|\w*)/g) || [];

  for (var i = 0; i < names.length; i++) {

    var option = /((\w*)-(\w*)|\w*)_(\w*-\w*-\w*|\w*-\w*|\w*)/.exec(names[i]);
    var value = this.recognizeValue(option[4]);

    switch(typeof option[3]){
      case "undefined": // Matches (option_value) for cool browsers
        this[option[1]] = value;
      break;
      case "string": // Matches (option-suboption_value)
        if(option[3].length){
          if(!this[option[2]] || this[option[2]].constructor != Object){
            this[option[2]] = {};
          }
          this[option[2]][option[3]] = value;
        } else if (option[2].length == 0 && jQuery.browser.msie){
          /* All good browsers match: /((\w*)-(\w*)|\w*)_(\w*)/ > ["option_value", undefined, undefined, "value"]
             but IEs matches > ["option_value", "", "", "value"] */
          this[option[1]] = value;
        }
      break;
    };

  }

};

DhoniShow.prototype.options.prototype.recognizeValue = function(value){
  if( /true|false/.test(value) ){
    return value = !!( value.replace(/false/, "") ); // Wild hack
  } 
  if( (/dot/).test(value) ){
    return value = Number( value.replace(/dot/, ".") );
  }
  return value;
};
// ###########################################################################

DhoniShow.helper = {

  image: function ( element ) {
    var img;
    element = (typeof element == "object" && element.nodeType == "undefined" || element.length) ? element[0] : element;
    if(element.nodeName.toLowerCase() != "img") {
      if((img = jQuery(element).find("img")) || (img = jQuery(element).filter("img"))){ 
        if(img.length > 0){
          element = img[0];
        }
      }
    }
    return element;
  },

  delayed_image_load: function ( image, func, scope, args ) {
    var src = image.attr("src");
    image.attr("src", "#");
    image.bind("load", function (){
      if(args) {
        var arg = jQuery.makeArray(args);
        arg.push({width: this.width, height: this.height});
      }
      func.apply(scope, arg || []);
      jQuery(this).unbind("load");
    });
    image.attr("src", src);
  },

  delayed_dimensions_load: function(dimensions, func, scope, args) {
    var image;
    if(!!!dimensions.width && !!!dimensions.height){
      if( ( image = jQuery(this.image(scope)) ).length > 0 ) { 
        this.delayed_image_load(image, func, scope, args);
      }
    }
  },
    
  to_number: function(string){
    return new Number(string.replace(/dot|px/, ""));
  }
};

// ###########################################################################

DhoniShow.prototype.dom = function(element, parent){
  this.parent = parent;
  this.element = jQuery(element);
  this.saveChildren();
  this.placeholders();
  this.invokeModules();

  this.setUpdaters();
};

DhoniShow.prototype.dom.prototype = {
  template : ['<div class="dhonishow-aligner">', // Need this for the align IE6 support, cause double class selectors doesn't work
      '<div class="dhonishow-effect-helper"><ol class="dhonishow-elements">@images</ol></div>',
      '<p class="dhonishow-alt">@alt</p>',
      '<div class="dhonishow-paging-buttons">',
        '<div class="dhonishow-theme-helper">',
          '<a class="dhonishow-next-button" title="Next">Next</a>',
          '<p class="dhonishow-paging">@current/@allpages</p>',
          '<a class="dhonishow-previous-button" title="Previous">Back</a>',
        '</div>',
      "</div>",
    '</div>'].join(""),
  
  setUpdaters: function() {
    this.parent.queue.register("updaters", this, this.alt, this.giveModluePlaceholder("alt"));
    this.parent.queue.register("updaters", this, this.current, this.giveModluePlaceholder("current"));
    this.parent.queue.register("updaters", this, this.allpages, this.giveModluePlaceholder("allpages"));
  },
  
  saveChildren: function(){
    this.children = jQuery(this.element).children();
    this.element.text("");
  },

  placeholders: function() {
    var modulePlaceholders = {};
    jQuery(this.element).append(this.template.replace(/@(\w*)/g, function(searchResultWithExpression, searchResult){
      modulePlaceholders[searchResult] = ".dhonishow_module_"+searchResult;
      return '<span class="dhonishow_module_'+searchResult+'"></span>';
    }));
    return this.modulePlaceholders = modulePlaceholders;
  },

  invokeModules: function(){
    for(var module in this.modulePlaceholders)
      this[module](this.giveModluePlaceholder(module));
  },

  giveModluePlaceholder: function(name){
    return jQuery(this.element).find(this.modulePlaceholders[name]);
  },

  images: function(placeholder){
    var _this = this;
    this.children || [];
    
    placeholder.replaceWith(this.children);
    this.elements = jQuery(this.children).wrap(arguments.callee.wrapper).parent();
  },

  alt: function(placeholder){
    var alt, title, src;
    if(alt = jQuery(this.elements[this.parent.current_index]).find("*[alt]").attr("alt")){
      if(jQuery(alt).filter("*").length){ placeholder.each(function(){ this.innerHTML = alt; }); return; }
      placeholder.text(alt);
      return;
    }
    if(title = jQuery(this.elements[this.parent.current_index]).find("*[title]").attr("title")){
      placeholder.text(title);
      return;
    }
    if(src = jQuery(this.elements[this.parent.current_index]).find("*[src]").attr("src")){
      var src = src.split('/');
      placeholder.text(src[src.length-1]);
      return;
    }
  },

  current: function(placeholder){
    placeholder.text(this.parent.current_index+1);
  },

  allpages: function(placeholder){
    placeholder.text(this.elements.length);
  }
};

DhoniShow.prototype.dom.prototype.images.wrapper = "<li class='element'></li>";

// ###########################################################################

DhoniShow.fn = {};

// ###########################################################################

DhoniShow.prototype.queue.prototype.moduleQueue.push("preloader");

DhoniShow.fn.preloader = function(value, parent){
  this.parent = parent;
  this.value = value;
  this.elements = [];
  
  this.parent.queue.register("loaded_one", this, this.loadedOne);
};

// ###########################################################################

DhoniShow.fn.preloader.prototype = {
  loadedOne: function ( position ) {
    if(this.elements.push(position) == this.parent.dom.elements.length)
      this.parent.queue.invokeAll("loaded_all");
  }
};

// ###########################################################################

DhoniShow.prototype.queue.prototype.moduleQueue.push("duration");

DhoniShow.fn.duration = function (value, parent) {
  if(value == 0) parent.options.duration = 0.01;
};

// ###########################################################################

DhoniShow.prototype.queue.prototype.moduleQueue.push("hide");

DhoniShow.fn.hide = function(value, parent){
  this.parent = parent;

  if(value == undefined || !value.buttons){
    this.parent.queue.register("updaters", this, this.previous_button).call(this);
    this.parent.queue.register("updaters", this, this.next_button).call(this);
  }
};

DhoniShow.fn.hide.prototype = {

  previous_button: function(){
    if(!this.parent.dom.previous_button)
      this.parent.dom.previous_button = this.parent.dom.element.find(".dhonishow-previous-button");
    
    this.parent.dom.previous_button.css("visibility", "hidden");
    if( this.parent.current_index != 0) this.parent.dom.previous_button.css("visibility", "");
  },

  next_button: function() {
    if(!this.parent.dom.next_button)
      this.parent.dom.next_button = this.parent.dom.element.find(".dhonishow-next-button");

    this.parent.dom.next_button.css("visibility", "hidden");
    if( this.parent.current_index != (this.parent.dom.elements.length - 1)) this.parent.dom.next_button.css("visibility", "");
  },

  not_current: function(){
    var element, current = this.parent.current_index;

    jQuery(this.parent.dom.elements).each(function(index){
      if(index != current)
        jQuery(this).hide();
      else
        element = jQuery(this);
    });

    return element;
  }
};

// ###########################################################################

DhoniShow.prototype.queue.prototype.moduleQueue.push("effect");

DhoniShow.fn.effect = function(effectName, parent){
  this.parent = parent;
  var args = effectName.split("-");
  if(args.length > 1) { effectName = args[0]; args.shift(); }
  this.effect = new DhoniShow.fn.effect.fx[effectName](this, args.join("-"));
  this.parent.queue.register("loaded_all", this.effect, this.effect.center);

  this.addObservers();
};

DhoniShow.fn.effect.prototype = {
  addObservers: function(){
    this.parent.dom.element.find(".dhonishow-previous-button").bind("click", this, this.previous);
    this.parent.dom.element.find(".dhonishow-next-button").bind("click", this, this.next);
  },

  next: function(event){
    var _this = event.data;
    if(!_this.parent.animating()){
      _this.update(_this.parent.current_index, ++_this.parent.current_index);
    }
  },

  previous: function(event){
    var _this = event.data;
    if(!_this.parent.animating()){
      _this.update(_this.parent.current_index, --_this.parent.current_index);
    }
  },

  update: function(next, current){
    this.parent.queue.invokeAll("updaters");
    this.effect.update(
        jQuery(this.parent.dom.elements[next]), 
        jQuery(this.parent.dom.elements[current]),
        this.parent.options.duration
    );
  }
};

// ###########################################################################

DhoniShow.fn.effect.fx = {};

// ###########################################################################

DhoniShow.fn.effect.fx.appear = function(parent, options){
  this.parent = parent;
};

DhoniShow.fn.effect.fx.appear.prototype = {
  update: function(next_element, current_element, duration){
    current_element.animate({ opacity: "toggle" }, duration*1000);
    next_element.animate({ opacity: "toggle" }, duration*1000);
  },
  center: function(hide){
    if(!hide) this.parent.parent.hide.not_current();
    
    var center = this.parent.parent.center;

    this.parent.parent.dom.element.css({width: center.dimensions.max.width+"px"});
    this.parent.parent.dom.elements.parent().css({height: center.dimensions.max.height+"px"});

    if(center.value) {
      if(center.value.elements || center.value.elements == undefined) {
        this.parent.parent.dom.elements.each(function(index){
          jQuery(this).css(center.dimensions[index].center);
        });
      }

      if(center.value.width) this.parent.parent.dom.element.css({width: center.value.width+"px"});
      if(center.value.height) this.parent.parent.dom.elements.parent().css({height: center.value.height+"px"});
    }
  }
};

// ###########################################################################

DhoniShow.fn.effect.fx.resize = function(parent, options){
  this.parent = parent;
};

DhoniShow.fn.effect.fx.resize.prototype = {
  update: function(next_element, current_element, duration){
    var dimensions = this.parent.parent.center.dimensions[this.parent.parent.current_index];

    this.parent.parent.dom.elements.parent().animate( { height: dimensions.height }, duration * 1000 );
    this.parent.parent.dom.element.animate( { width: dimensions.width }, duration * 1000 );

    current_element.animate({ opacity: "toggle" }, duration*1000);
    next_element.animate({ opacity: "toggle" }, duration*1000);
  },

  center: function(){
    var elements = jQuery(this.parent.parent.dom.elements.get().reverse());
    for (var i=0; i < elements.length; i++) {
      jQuery(elements[i]).css( "z-index", i + 1 );
      if(i != (elements.length-1 - this.parent.parent.current_index)) jQuery(elements[i]).hide();
    }
    
    var center = this.parent.parent.center;

    this.parent.parent.dom.element.css({width: center.dimensions[this.parent.parent.current_index].width+"px"});
    this.parent.parent.dom.elements.parent().css({height: center.dimensions[this.parent.parent.current_index].height+"px"});
  }
};

// ###########################################################################

DhoniShow.fn.effect.fx.slide = function(parent, options){
  this.parent = parent;
  this.element = this.parent.parent.dom.element.find(".dhonishow-elements");
  this.options = (function(){
    if(options == "top") return { dimension: "height", side: "top" };
    return { dimension: "width", side: "left" };
  })();
};

DhoniShow.fn.effect.fx.slide.prototype = {
  /*
    FIXME Clicking while autoplay doesn't work properly
  */
  
  update: function(next_element, current_element, duration, event) {
    var element_option = {};
    var element = current_element;
    if(this.parent.parent.options.autoplay){ element = next_element; } // Doesn't work on klick
    element_option[this.options.side] = -DhoniShow.helper.to_number(element.css(this.options.side));
    this.element.animate(element_option, duration*1000);
  },

  center: function(){
    
    DhoniShow.fn.effect.fx.appear.prototype.center.apply(this, [true]);

    var dimension = this.options.dimension;
    var side = this.options.side;
    var offset = 0;

    this.dimensions = this.parent.parent.center.dimensions.max;

    var step = this.dimensions[dimension];
    this.parent.parent.dom.elements.each(function(){ 
      this.style[side] = offset+"px";  offset += step;
    });

    var element_option = {};
    element_option[side] = 0;
    element_option[dimension] = this.parent.parent.dom.elements.length*this.dimensions[dimension];

    this.element.css(element_option);

    this.parent.parent.dom.element.find(".dhonishow-effect-helper").css({
      height: this.dimensions.height,
      width: this.dimensions.width
    });
  }
};

// ###########################################################################


DhoniShow.prototype.queue.prototype.moduleQueue.push("autoplay");

DhoniShow.fn.autoplay = function(value, parent){
  this.parent = parent;
  if(value){
    this.create(value);
    this.parent.queue.register("updaters", this, this.reset);
  }
};

DhoniShow.fn.autoplay.prototype = {
  create: function(duration) {
    var _this = this;
    this.executer = setInterval(function(){
      _this.parent.current_index++;

      if(_this.parent.current_index == _this.parent.dom.elements.length) {
        _this.parent.effect.effect.update(
          jQuery(_this.parent.dom.elements[_this.parent.current_index = 0]), 
          jQuery(_this.parent.dom.elements[_this.parent.dom.elements.length-1]),
          _this.parent.options.duration
        );
      } else {
        _this.parent.effect.effect.update(
          jQuery(_this.parent.dom.elements[_this.parent.current_index]),
          jQuery(_this.parent.dom.elements[_this.parent.current_index-1]),
          _this.parent.options.duration
        );
      }
      _this.parent.queue.invokeAll("updaters");
    }, duration*1000);
  },

  reset: function () {
    clearInterval(this.executer);
    this.executer = null;
    this.create(this.parent.options.autoplay);
  }
};

// ###########################################################################

DhoniShow.prototype.queue.prototype.moduleQueue.push("center");

DhoniShow.fn.center = function(value, parent){
  this.parent = parent;
  this.value = value;
  this.dimensions = {
      max: {
        width: 0,
        height: 0
      }
  };

  this.set_dimensions();
};

DhoniShow.fn.center.prototype = {
  set_dimensions: function(){
    var _this = this;

    this.parent.dom.elements.each(function (index) {

      var dimensions = arguments[2] ? arguments[2] : {width: 0, height: 0};

      DhoniShow.helper.delayed_dimensions_load(dimensions, arguments.callee, this, arguments);

      if(!dimensions.width || !dimensions.height) return;


      _this.dimensions.max.height = (dimensions.height > _this.dimensions.max.height) ? dimensions.height : _this.dimensions.max.height;
      _this.dimensions.max.width = (dimensions.width > _this.dimensions.max.width) ? dimensions.width : _this.dimensions.max.width;

      _this.dimensions[index] = {
        width: dimensions.width,
        height: dimensions.height
      };

      if(_this.value == true || _this.value.elements) {
        for(var index in _this.dimensions){
          if(index != "max"){
            _this.dimensions[index].center = {
              paddingLeft: ( (_this.dimensions.max.width - _this.dimensions[index].width) / 2 )+"px",
              paddingTop: ( (_this.dimensions.max.height - _this.dimensions[index].height) / 2 )+"px"
            };
          }
        }
      } else if((_this.value.width || _this.value.height) && (_this.value.elements || _this.value.elements == undefined)) {
        for(var index in _this.dimensions){
          
          var element_dimensions = {
            width : new Number((_this.value.width) ? _this.value.width : _this.dimensions.max.width),
            height : new Number((_this.value.height) ? _this.value.height : _this.dimensions.max.height)
          };
          
          var css = {};
          var offsetWidth = ( _this.dimensions[index].width - element_dimensions.width ) / 2;
          var offsetHeight = ( _this.dimensions[index].height - element_dimensions.height ) / 2;

          if(offsetWidth > 0) {
            css.paddingLeft = 0;
            css.marginLeft = offsetWidth - offsetWidth - offsetWidth + "px";
          } else css.paddingLeft = offsetWidth - offsetWidth - offsetWidth + "px";

          if(offsetHeight > 0){
            css.paddingTop = 0;
            css.marginTop = offsetHeight - offsetHeight - offsetHeight + "px";
          } else css.paddingTop = offsetHeight - offsetHeight - offsetHeight + "px";

          _this.dimensions[index].center = css;
        }
      }
      _this.parent.queue.invokeAll("loaded_one", index);

    });
  }
};

// ###########################################################################

DhoniShow.prototype.queue.prototype.moduleQueue.push("align");

DhoniShow.fn.align = function(options, parent){
  var alignHelper = parent.dom.element.find(".dhonishow-aligner");
  for(var option in options){
    switch(option){
      case "alt":
        parent.dom.element.addClass("align-"+option+"_"+options[option]);
      break;
      default:
        alignHelper.addClass("align-"+option+"_"+options[option]);
      break;
    }
  }
};

// ###########################################################################

DhoniShow.prototype.queue.prototype.moduleQueue.push("thumbnails");
DhoniShow.fn.thumbnails = function(option, parent) {
  this.parent = parent;

  if(option){ 
    this.option = option.constructor == Boolean ? 50 : option.split("-")[1];
    this.parent.queue.register("updaters", this, this.set_active);
    this.parent.queue.register("loaded_all", this, this.loaded_all);
  }
};

DhoniShow.fn.thumbnails.prototype = {
  loaded_all: function(){
    this.create_dom();
    this.center();
    this.addObservers();
    this.set_active();
  },

  create_dom: function() {
    this.lis = this.parent.dom.elements.clone().removeClass("element").css({padding: "", top: "", left: "", display: ""});
    this.lis.children().each(function(index){ jQuery(this).wrap('<a href="'+this.src+"#"+index+'"></a>'); });

    this.element = jQuery("<ol class='dhonishow-thumbnails'></ol>").append(this.lis);
    this.parent.dom.element.find(".dhonishow-aligner").append(this.element);
  },

  center: function(){
    var _this = this;
    var liDimension = {width: this.lis.width(), height: this.lis.height()};

    jQuery.each(this.parent.center.dimensions, function(index){
      if(index != "max"){
        jQuery(_this.lis[index]).children().css({
          left: (liDimension.width/2 - this.width/2)+"px",
          top: (liDimension.height/2 - this.height/2)+"px"
        });
      }
    });
  },

  addObservers: function(){
    var _this = this;
    this.lis.find("a").bind("click", function(event){
      var index = new Number(this.href.split("#")[1]),
      current_index = _this.parent.current_index;
      
      _this.parent.current_index = index;
      _this.parent.effect.update(current_index, index);
      return false;
    });
  },
  
  set_active: function(){
    this.lis.removeClass("active").eq(this.parent.current_index).addClass("active");
  }
};

// ###########################################################################

jQuery.fn.dhonishow = function(options){
  return jQuery.each(this, function(index){
    new DhoniShow(this, options, index);
  });
};

jQuery(function(){jQuery(".dhonishow").dhonishow();});
