/*
  Bubble plugin.
  Written by Egor Hmelyoff (hmelyoff@gmail.com)

  Global object:
    bubble

  Api:
    bubble.show(@data, @settings) — show bubble

    bubble.close(@settings) — close bubble

    bubble.append(node, @settings) — append data to bubble
      — return: uid:Number to show bubble later

    bubble.onload(func:Function) — execute func after bubble loading

  Params:
    @data:
      — "remote:url/page"  :String     — load page by ajax
      — "image:url/image]" :String     — preload and show any image
      — "html data string" :String     — create object from html data and show bubble with it (not working now)
      — "any text"         :String     — show bubble with this text (not working now)

      — uid                :Number     — show node generated by bubble.append

      — node               :DOM/Object — append node and show it

    @settings:Object
      — from               :DOM/Object — animate bubble from this node/size object (undefined — show from center or current state)
        { width: px, height: px, left: px, top: px }

      — to                 :DOM/Object — animate bubble to this node/size object (undefined — show to center, normal mode)
        { width: px, height: px, left: px, top: px }

      — message            :String     — Preload message
      — success            :Boolean    — Set preload icon to success

      — event              :Event      — to be able catch users pressed keys and so on (now using for make animation slower with Alt key)
      — reload             :Boolean    — if true reload ajax data for each "remote" bubble show
      — serialize          :FROM_ID    — ID of form to serialize and send with "remote" request
      — ajax               :Object     — object to extend ajax request object (look jQuery.ajax) (not working now)

      — oncreate(@node)    :Function   — call after data append by bubble.append in bubble context
      — onclose()          :Function   — call after bubble closed

  Example:
    bubble.show("remote:/login", { event: event, from: this })

  TODO:
    — centering content over animation
    — finishing list above
    — initialize bubble on show if it still does not initialized

*/


(function($) {

  var onload = [];
  var $this = this;

  this.bubble = {};
  this.bubble.onload = function(func){
    onload.push(func);
  }

  // $(window).load(function(){
  $(document).ready(function(){
    $this.bubble = new b_bubble();
    bubble.postCreate();
  })

  var b_bubble_OPTIONS = {
    className: "b-bubble",
    classFixed: "b-bubble-fixed",

    opacityUnderLayer: 0.7,

    speed: 400, //ms
    speedSlow: 4000, //ms

    shadowSize: 43,
    alwaysOnTop: true,

    animation: "swing",
    animationClose: "easeInBack",

    preload: {
      width: 250,
      height: 150
    },

    template:
      '<div>' +
        '<div class="b_c">' +
          '<div class="b_t"><div><div></div></div></div>' +
          '<div class="b_r"><div><div></div></div></div>' +
          '<div class="b_b"><div><div></div></div></div>' +
          '<div class="b_l"><div><div></div></div></div>' +
          '<div class="b_content_out">' +
            '<div class="b_close" title="Esc"></div>'+
            '<div class="b_content">' +
            '</div>' +
          '</div>' +
        '</div>' +
      '</div>'
  }

  this.b_bubble = function(){
    return this.init.apply(this, arguments);
  }

  b_bubble.prototype = {
    init: function(options){
      this.options = $.extend(b_bubble_OPTIONS, options ? options : {});

      this.is = {
        created: false,
        opened: false,
        closing: false,
        animate: false,
        loading: false
      }

      this.queue = [];
      this.cache = [];
      this.events = [];

      this.create();
    },
    create: function(){
      if(!this.is.created){

        this.ptr = $(this.options.template)
          .css({ left: -9999, top: -9999 })
          .addClass(this.options.className)

        this.ptr.prependTo(document.body);

        this.o = {
          content: this.ptr.find(".b_content"),
          close: this.ptr.find(".b_close")/*,
          bc: this.ptr.find(".b_c"),
          eh: this.ptr.find(".b_t div").height(),
          ew: this.ptr.find(".b_l div").width(),

          oh: this.ptr.find(".b_t, .b_b"),
          ot: this.ptr.find(".b_r div div, .b_l"),
          ob: this.ptr.find(".b_r div, .b_l div").not(".b_r div div, .b_l div div"),

          ow: this.ptr.find(".b_r, .b_l"),
          or: this.ptr.find(".b_t div, .b_b div").not(".b_t div div, .b_b div div"),
          ol: this.ptr.find(".b_t div div, .b_b")*/
        }
/*
        this.o.pt = parseInt(this.o.bc.css("padding-top"));
        this.o.pl = parseInt(this.o.bc.css("padding-left"));
*/

        //this.ptr.hide();

        if($.browser.msie && $.browser.version < 7)
          this.ptr.wrap($("<div>").addClass(this.options.classFixed).hide())

        this.underLayer = $("<div>").addClass("b-bubble-under").hide();
        this.underLayer.insertAfter(this.ptr);


        var $this = this;
        this.o.close.add(this.underLayer).click(function(evt){
          $this.close({ event: evt });
        })

        $(document).keypress(function( event ){
          if(event.keyCode == 27 && $this.is.opened){
            $this.close({ event: event });
            return false;
          }
        })



        this.is.created = true;
      }
    },

    onload: function(func){
      if($.isFunction(func))
        func.call(window);
    },
    postCreate: function(){
      if(onload.length){
        for (var i=0; i < onload.length; i++) {
          if($.isFunction(onload[i]))
            onload[i].call(window);
        };
        onload = [];
      }
    },



    addQueue: function(func){
      if(!this.queueCount)
        this.queueCount = 0;

      this.queue.push(func);
      this.queueCount++;
      this.check();
    },
    check: function(){
      if(!this.is.animate && this.queue.length){
        this.queue[0].call(this);
        this.queue.shift();
      }
    },
    inCache: function(data){
      for (var i=0; i < this.cache.length; i++) {
        if(this.cache[i].uid && this.cache[i].uid == data)
          return this.cache[i];

        if(this.cache[i].src && this.cache[i].src == data)
          return this.cache[i];

        if(this.cache[i].url && this.cache[i].url == data)
          return this.cache[i];

        if(this.cache[i].node && this.cache[i].node == data)
          return this.cache[i];
      };
      return false;
    },
    cleanCache: function(cache){
      for (var i=0; i < this.cache.length; i++) {
        if(this.cache[i] == cache)
          break;
      }
      this.cache.splice(i, 1);
    },


    animate: function(settings){
      var $this = this;

      if(!this.is.animate){
        this.is.animate = true;

        if(!this.is.opened)
          this.ptr.css({ left: -9999, top: -9999 }).show();

        if(this.is.opened)
          settings.from = null;

        from = this.normalize(settings.from);
        if(this.is.opened){
          $.extend(from, { width: this.sizes.w, height: this.sizes.h })
        }

        this.sizes = {
          w: from.width,
          h: from.height,
          l: from.left,
          t: from.top
        }

        this.o.content.css({
          width: Math.round(this.sizes.w),
          height: Math.round(this.sizes.h)
        });

        var top = from.top - this.ptr.height()/2;
        if(this.options.alwaysOnTop && this.options.shadowSize)
          if(top < -this.options.shadowSize) top = -this.options.shadowSize;

        this.ptr.css({
          left: from.left - this.ptr.width()/2,
          top: top
        })

        if(!$.browser.msie && !this.is.opened)
          this.ptr.css({ opacity: 0 })

        // this.setLimit();

        to = this.normalize(settings.to);
        this.o.content.css({ overflowY: "hidden" });

        this.underLayer.css({ opacity: this.options.opacityUnderLayer }).show();

        var speed = this.options.speed;
        try{
          if(settings.event && settings.event.altKey === true)
            speed = this.options.speedSlow;
        } catch(e){}


        var animation = this.options.animation;
        if(this.is.closing)
          animation = this.options.animationClose;

        this.oAni = new classAnimate(function(now, prev){
          $this.sizes = {
            w: $this.sizes.w + ((to.width-from.width)*(now-prev)),
            h: $this.sizes.h + ((to.height-from.height)*(now-prev))
          }

          $this.o.content.css({
            width: Math.round($this.sizes.w),
            height: Math.round($this.sizes.h)
          });

          var top = from.top + (to.top-from.top)*now - $this.ptr.height()/2;
          if($this.options.alwaysOnTop)
            if(top < -$this.options.shadowSize) top = -$this.options.shadowSize;

          $this.ptr.css({
            left: from.left + (to.left-from.left)*now - $this.ptr.width()/2,
            top: top
          })

          if(!$.browser.msie && !$this.is.opened)
            $this.ptr.css({ opacity: to.opacity*now })

          if(!$.browser.msie && $this.is.closing)
            $this.ptr.css({ opacity: (1-now) });

          // $this.setLimit();

        }, speed, animation, function(){

          if(settings.message && $this.is.loading){
            $this.o.content.append("<span>" + settings.message + "</span>");
          }

          if(to && to.overflowY){
            $this.o.content.css({ overflowY: to.overflowY })
          }

          $this.is.animate = false;
          $this.is.opened = true;
          $this.check();

          if(this.events && this.events.onshow && $.isFunction(this.events.onshow))
            this.events.onshow.call(this);
        })

      }


    },
    setLimit: function(){
      var t = Math.round(this.o.pt + this.sizes.h/2);
      if(t > this.o.eh) t = this.o.eh;

      var l = Math.round(this.o.pl + this.sizes.w/2);
      if(l > this.o.ew) l = this.o.ew;

      this.o.oh.css({ height: t });
      this.o.ot.css({ top: t });
      if($.browser.msie && $.browser.version < 7 && this.o.ob.parent().height() % 2) t--;
      this.o.ob.css({ bottom: t });

      this.o.ow.css({ width: l });
      this.o.ol.css({ left: l });
      if($.browser.msie && $.browser.version < 7 && this.o.or.parent().width() % 2) l--;
      this.o.or.css({ right: l });

    },
    normalize: function(obj){
      var defs = { // default coords and sizes
        width: 30,
        height: 30,
        left: $(window).width()/2,
        top: $(window).height()/2,
        opacity: 1
      }

      if(obj){
        var jobj = $(obj);
        if(obj.length > 0 || obj.tagName){ //domNode
          defs.width = jobj.width() > defs.width ? jobj.width() : defs.width;
          defs.height = jobj.height() > defs.height ? jobj.height() : defs.height;

          var _offset = jobj.offset();
          defs.left = _offset.left+defs.width/2;
          defs.top = _offset.top+defs.height/2-$(window).scrollTop();

        } else { // size object
          if(obj.width < 30) delete obj.width;
          if(obj.height < 30) delete obj.height;
          $.extend(defs, obj)
        }
      }

      if( defs.height > $(window).height()-200){
        defs.height = $(window).height()-200;
        defs.overflowY = "auto";
      }

      return defs;
    },


    show: function( data, settings ){
      var $this = this;
      var jData = $(data);

      if( $.browser.msie && $.browser.version < 7 )
        this.ptr.parents("." + this.options.classFixed).show();

      if( settings && settings.onclose && $.isFunction(settings.onclose) )
        this.events.onclose = settings.onclose;
      else
        this.events.onclose = null;

      switch( typeof data ){
        case "number":
          this.showByCacheId(data, settings);
          break;

        case "string":

          if(jData.length > 0){
            // console.log("TODO: animate -> string object")

          } else if(data.split(":")[0] == "remote"){
            var url = data.split(":")[1];
            this.showRemote(url, settings);

          } else if(data.split(":")[0] == "image"){
            var src = data.split(":")[1];
            this.showImage(src, settings);

          } else {
            this.animate(from, to ? to : { width: 200, height: 200 });
            // console.log("TODO: animate -> text")

          }

          break;

        case "object":
          if(jData.length > 0){
            this.showNode(jData, settings);
          }

          break;

      }

    },
    close: function(settings){
      if(!this.is.closing && this.is.opened){
        this.addQueue(function(){
          this.is.closing = true;

          if(this.is.loading)
            this.preload(false);

          if(!settings.to) settings.to = {};
          $.extend(settings.to, { width: 0, height: 0 })
          this.animate(settings);
        });
        this.addQueue(function(){
          if($.browser.msie && $.browser.version < 7)
            this.ptr.parents("." + this.options.classFixed).hide();

          this.underLayer.hide();
          //this.ptr.hide();
          this.ptr.css({ left: -9999, top: -9999 })
          this.o.content.children().hide();

          this.is.opened = false;
          this.is.closing = false;

          if(this.events && this.events.onclose && $.isFunction(this.events.onclose))
            this.events.onclose.call(this);

        });
      }
    },
    preload: function(settings){
      if(settings){
        this.addQueue(function(){
          this.is.loading = true;

          this.o.content.children().hide();
          this.ptr.addClass("b-bubble-loading");
          if(settings.success)
            this.ptr.addClass("b-bubble-loading-success");

          if(!settings.to) settings.to = {};
          $.extend(settings.to, this.options.preload);
          this.animate(settings);
        });
      } else {
        this.ptr.removeClass("b-bubble-loading");
        this.ptr.removeClass("b-bubble-loading-success");
        this.is.loading = false;
        this.o.content.children().hide();
      }

    },
    append: function(node, settings){
      var uid = (new Date()).getTime() + Math.round(Math.random()*9999);
      node = $(node);

      // if(node.parent().length)
      //   node = node.clone(true);

      node.appendTo(this.o.content);
      this.dimension();

      if(settings && settings.oncreate && $.isFunction(settings.oncreate))
        settings.oncreate.call(this, node);

      var w = node.outerWidth();
      var h = node.outerHeight();

      node.hide();
      this.dimension(false);

      this.cache.push({ uid: uid, node: node, w: w, h: h });

      return uid;

    },
    dimension: function(hide){
      if($.browser.msie && $.browser.version < 7)
        if(hide === false){
          this.ptr.parent().hide();
        } else {
          this.ptr.parent().show()
        }
    },



    // data types
    showImage: function(src, settings){
      var cache = this.inCache(src);
      if(cache){
        this.preload(false);

        this.o.content.html("<img src='" + cache.src + "' />");
        if(!settings.to) settings.to = new Object();
        $.extend(settings.to, { width: cache.w, height: cache.h });
        this.animate(settings);

      } else {
        var $this = this;
        this.preload(settings);

        var image = $("<img src='" + src + "' />");
        if($.browser.msie && $.browser.version < 7)
          $("." + this.options.classFixed).append(image);
        else
          $(document.body).append(image);
        image.css({ position: "absolute", left: -9999, top: -9999, border: "none" });

        image.load(function() {
          var w = image.width();
          var h = image.height();
          $this.cache.push({ src: src, w: w, h: h })

          $this.addQueue(function(){
            this.preload(false);
            this.o.content.html("<img src='" + src + "' />");
            $.extend(settings.to, { width: w, height: h })
            settings.from = null;
            this.animate(settings);
          })
          image.remove();
        });
      }
    },

    // data types
    showRemote: function(url, settings){
      var cache = this.inCache(url);

      if(cache && !settings.reload){
        this.preload(false);

        cache.node.css({'display': 'block'});
        if(!settings.to) settings.to = new Object();
        $.extend(settings.to, { width: cache.w, height: cache.h });

        this.animate(settings);

      } else {
        var $this = this;

        if(cache && settings.reload)
          this.cleanCache(cache);

        var request = {
          url: url,
          dataType: "html",
          success: function(data){
            var node = $(data).css({ visibility: "hidden", position: "absolute", left: -9999, top: -9999 });

            var queue = $this.queueCount;

            node.appendTo($this.o.content);

            var w = node.outerWidth();
            var h = node.outerHeight();
            node.css({'display': 'none'});

             if(w*h == 0){
               if($this.queueCount == queue){
                 $this.close(settings);
               }
             } else {
               if(!settings.reload)
                 $this.cache.push({ url: url, node: node, w: w, h: h });

               $this.addQueue(function(){
                 this.preload(false);
                 node.removeAttr("style");

                 $.extend(settings.to, { width: w, height: h })
                 settings.from = null;
                 this.animate(settings);
               })
             }


          }
        }

        if(settings.serialize){
          var form = $("#" + settings.serialize);
          var data = { data: form.serialize(), type: form.attr("method") ? form.attr("method").toUpperCase() : "POST" };
          $.extend(request, data);
        }

        this.preload(settings);
        $.ajax(request)

      }
    },

    showNode: function(node, settings){
      var cache = this.inCache(node.get(0));
      if(cache){
        this.showByCacheId(cache.uid, settings);
      } else {
        var uid = this.append(node);
        this.cache.push({ node: node.get(0), uid: uid });
        this.showByCacheId(uid, settings);
      }
    },


    showByCacheId: function(uid, settings){
      var cache = this.inCache(uid);
      if(cache){
        this.preload(false);

        cache.node.show();

        if(settings && settings.onshow && $.isFunction(settings.onshow))
          settings.onshow.call(this, cache.node);

        if(!settings.to) settings.to = new Object();
        $.extend(settings.to, { width: cache.w, height: cache.h });
        this.animate(settings);
      }
    }

  }

})(jQuery);

