(function($) {
  var canvasElements = [];
  var pollCounter = 0;
  var plugins = {};

  function Area(canvas) {
    var stack = [];
    
    $.extend(this, {
      width: canvas.width, height: canvas.height, ctx: canvas.getContext("2d"),
      
      save: function() {
        this.ctx.save();
        stack.push({ width: this.width, height: this.height });
      },
      
      restore: function() {
        this.ctx.restore();
        $.extend(this, stack.pop());
      }
    });
  }
  
  var Plugin = (function() {
    var shrink = function(area, steps) {
      area.ctx.translate(steps, steps);
      area.width -= 2 * steps;
      area.height -= 2 * steps;
    };
    return {
      action:{ paint:function(){} },  // provide a NOP "plugin"
      shrink: shrink,
      defaultShrink: shrink,
      setAction: function(action) { this.action = action; }
    };
  })();
  
  function newPlugin(hash, opts) {
    return $.extend({}, Plugin, hash, { opts: opts, savedOpts: opts });
  }
  
  function pluginFromPlugins(plugins) {
    return newPlugin({
      paint: function(area) {
        area.save();
        this.action.opts = $.extend(true, this.action.savedOpts);
        $.each(plugins, function() { this.paint(area); });
        area.restore();
      },
      
      setAction: function(action) {
        this.action = action; // should call super if it existed ...
        $.each(plugins, function() { this.action = action; });
      }
    });
  }
  var pluginFromApplications = pluginFromPlugins; // it just does the same ...
  
  function pluginFromName(name, opts) {
    var plugin = plugins[name];
    if (!plugin) throw "Unknown plugin: " + name;
    opts = $.extend({}, plugin.defaultOpts || {}, opts);
    return newPlugin(plugin, opts);
  }
  
  function parse(s) {
    s += " ";
    var index = 0;
    
    function err(m) { msg = m + " at " + index + ": ..." + s.substring(index) + "\nin " + s; alert(msg); throw msg; }
    function cur() { return s.charAt(index); }
    function next() { if (index > s.length) throw("Unexpected end"); return s.charAt(index + 1) }
    function eat() { return s.charAt(index++); }
    function skipWhite() { while (/\s/.exec(cur())) eat(); }
    function check(c) {
      skipWhite(); 
      for (var i=0; i<c.length; ++i) {
        if (cur() != c.charAt(i)) err("Expected '" + c.charAt(i) + "' found '" + cur() + "'"); 
        eat();
      }
    }

    //var parseApplications; // forward reference
    
    function parseWord() {
      skipWhite();
      for (var word = []; /\w/.exec(cur()); word.push(eat()));
      return word.join("");
    }
    
    function parseNumber() {
      skipWhite();
      for (var n = []; /\d/.exec(cur()); n.push(eat()));
      return parseInt(n.join(""));
    }
    
    function parseString() {
      skipWhite();
      var s = [], start = cur();
      if (/[^\'\"]/.exec(start)) { err("String expected") }
      eat();
      while (cur() != start) { if (cur() == "\\") s.eat(); s.push(eat()); }
      check(start);
      return s.join("");
    }
    
    // Yeah, strange thing - this does the CSS value like parsing
    function parseValue() {
      skipWhite();
      for (var s = []; /[^;}]/.exec(cur()); s.push(eat()));
      return s.join("");
    }
    
    function parseLiteral() {
      skipWhite();
      if (/\d/.exec(cur())) return parseNumber();
      if (/['"]/.exec(cur())) return parseString();
      return parseValue();
    }
    
    function parseOpts() {
      check("{");
      skipWhite();
      var opts = {};
      while (cur() != "}") {
        var key = parseWord();
        check(":");
        opts[key] = parseLiteral();
        skipWhite();
        if (cur() == "}") break;
        check(";");
      }
      check("}");
      return opts;
    }
    
    function parsePlugin() {
      var name = parseWord();
      skipWhite();
      opts = cur() == "{" ? parseOpts() : {};
      return pluginFromName(name, opts);
    }
    
    function parsePlugins() {
      check("[");
      skipWhite();
      var plugins = [];
      while (cur() != "]") {
        plugins.push(parsePlugin());
        skipWhite();
      }
      check("]");
      return pluginFromPlugins(plugins);
    }

    function parseActors() {
      skipWhite();
      return cur() == "[" ? parsePlugins() : parsePlugin();
    }
    
    function parseAction() {
      var action;
      skipWhite();
      if (cur() == "(") {
        eat();
        action = parseApplications();
        check(")");
      } else {
        action = parsePlugin();
      }
      return action;
    }
    
    function parseApplication() {
      var actors = parseActors();
      check("=>");
      var action = parseAction();
      actors.setAction(action);
      return actors;
    }
    
    function parseApplications() {
      var applications = [];
      while (true) {
        applications.push(parseApplication());
        skipWhite();
        if (cur() != ",") break;
        check(",");
      }
      return pluginFromApplications(applications);
    }
    
    return parseApplications();
  }

  function checkResize(container, force) {
    var $container = $(container);
    var data = $container.data('liquid-canvas');
    if (!data) return;
    var canvas = data.canvas;
    var $canvas = $(canvas);
    var w = $container.outerWidth();
    var h = $container.outerHeight();
    
    if (force || canvas.width != w || canvas.height != h ||
        canvas.offsetTop != container.offsetTop || canvas.offsetLeft != container.offsetLeft) {
      pollCounter = 100;
      $canvas.css({ left: container.offsetLeft + "px", top: container.offsetTop + "px" });
      canvas.width = w;
      canvas.height = h;
      var area = new Area(canvas);
      area.save();
      data.paint(area);
      area.restore();
    }
  }

  function checkAllResize(force) {
    $.each(canvasElements, function() { checkResize(this, force); });
  }

  function poll(){
    checkAllResize();
    pollCounter--;
    if (pollCounter < 0) {
      pollCounter = 0;
      setTimeout(poll, 1000);
    } else {
      setTimeout(poll, 1000 / 60);
    }
  }

  jQuery.fn.extend({
    liquidCanvas: function(func, setaParams) {
	  setaParams = setaParams || {};
	  var id = setaParams.id;
	  var lado = setaParams.lado;
	  var setaTamanho = setaParams.setaTamanho;
	  lado = lado.replace('c','');
	  setaTamanho = setaTamanho || 10;
      var args = $.makeArray(arguments);
      this.each(function() {
	  	if(lado != ''){
		   var padding = '';
		   switch(lado.charAt(0)){
			   case 'l': padding = 'left';
			   break;
			   case 'b': padding = 'bottom';
			   break;
			   case 'r': padding = 'right';
			   break;
			   case 't': padding = 'top';
			   break;
		   } 
		   //if(!$(this).hasClass('tollTip-Rvaz-padding')){
			if($(this).attr('padding-type') != padding || $(this).attr('padding-size') != setaTamanho){
				var lastPadding = $(this).attr('padding-size') || 0;
				var lastType	= $(this).attr('padding-type') || '';
				if(lastType != '')
				$(this).css('padding-'+lastType, Number(($(this).css('padding-'+lastType)).replace('px','')) - lastPadding + 'px');
			    $(this).attr('padding-type', padding);
				$(this).attr('padding-size', setaTamanho);
		   		$(this).css('padding-'+padding, Number(($(this).css('padding-'+padding)).replace('px','')) + setaTamanho + 'px');
				//$(this).addClass('tollTip-Rvaz-padding');
		   }
	   }
		//$(this).removeClass('tollTip-Rvaz-content');
        var $canvas = $('<canvas id="tollTip-Rvaz-canvas-'+id+'" width="0" height="0" style="position:absolute; top:0px; left:0px;"></canvas>');
		
        var canvas;
        if (window.G_vmlCanvasManager) {
			//alert('vml');
          	$(this).before('<div id="tollTip-Rvaz-canvas-'+id+'" width="0" height="0" style="position:absolute; top:0px; left:0px;"></div>');
          	canvas = G_vmlCanvasManager.initElement($(this).prev("div").get(0));
        } else {
			$(this).before($canvas);
            canvas = $canvas.get(0);
        }
        
        var paint;
        if ($.isFunction(func)) {
          paint = func;
        } else {
          var plugin = parse(func)
          paint = function(area) { 
		  	  area.lado = lado;
			  area.setaTamanho = setaTamanho; 
			  plugin.paint(area); 
		  };
        }
        
        $(this).data("liquid-canvas", {
          "canvas": canvas,
          "paint": paint
        });
        $(this).css({ background: "transparent" });
        if ($(this).css("position") != "absolute") $(this).css({ position: "relative" });
        
        canvasElements.push(this);
        checkResize(this, true);
      });
    }
  });
  
  jQuery.extend({
    registerLiquidCanvasPlugin: function(plugin) {
      plugins[plugin.name] = $.extend({}, Plugin, plugin);
    }
  });
  
  $(document).ready(checkAllResize);
  poll();

  $.registerLiquidCanvasPlugin({
    name: "rect",
    paint: function(area) {
      area.ctx.beginPath();
      area.ctx.rect(0, 0, area.width, area.height);
      area.ctx.closePath();
      if (this.action) this.action.paint(area);  // for chaining
    }
  });
  $.registerLiquidCanvasPlugin({
    name: "roundedRect",
    defaultOpts: { radius:20},
    paint: function(area) {
	 
	  var Obj = area.obj;
	  var lado = area.lado;
	  var setaTamanho = area.setaTamanho;
      var ctx = area.ctx;
      var opts = this.opts;
	  var Xzero = lado.charAt(0) == 'l' ? setaTamanho : 0;
	  var Xmax = lado.charAt(0) == 'r' ? area.width - setaTamanho : area.width;
	  var Yzero = lado.charAt(0) == 't' ? setaTamanho : 0;
	  var Ymax = lado.charAt(0) == 'b' ? area.height - setaTamanho : area.height;
	  
      ctx.beginPath();
      ctx.moveTo(Xzero, Yzero + opts.radius);
	  // seta left
	  if(lado == 'l'){
		  ctx.lineTo(Xzero, (Ymax - opts.radius)/2 - (setaTamanho/2));
		  ctx.lineTo(Xzero - setaTamanho, (Ymax - opts.radius)/2 + (setaTamanho/2)/2 );
		  ctx.lineTo(Xzero, (Ymax - opts.radius )/2 + setaTamanho);
	  }
	  ctx.lineTo(Xzero, Ymax - opts.radius);
      ctx.quadraticCurveTo(Xzero, Ymax, Xzero + opts.radius, Ymax);
	  // seta bottom
	  if(lado == 'b'){
		  ctx.lineTo((Xmax - opts.radius)/2 - (setaTamanho/2), Ymax );
		  ctx.lineTo((Xmax - opts.radius)/2 + (setaTamanho/2)/2, Ymax + setaTamanho);
		  ctx.lineTo((Xmax - opts.radius )/2 + setaTamanho, Ymax);
	  }
	  if(lado == 'bl'){
		  ctx.lineTo((Xzero + opts.radius + 10), Ymax );
		  ctx.lineTo((Xzero + opts.radius + 10) - (setaTamanho/2), Ymax + setaTamanho);
		  ctx.lineTo((Xzero + opts.radius + 10) + (setaTamanho/2), Ymax);
	  }
	  if(lado == 'br'){
		  ctx.lineTo((Xmax - opts.radius - 10) - (setaTamanho/2), Ymax );
		  ctx.lineTo((Xmax - opts.radius - 10) + (setaTamanho/2), Ymax + setaTamanho);
		  ctx.lineTo((Xmax - opts.radius - 10), Ymax);
	  }
      ctx.lineTo(Xmax - opts.radius, Ymax);
      ctx.quadraticCurveTo(Xmax, Ymax, Xmax, Ymax - opts.radius);
	  // seta right
	  if(lado == 'r'){
		  ctx.lineTo(Xmax, (Ymax - opts.radius )/2 + setaTamanho);
		  ctx.lineTo(Xmax + setaTamanho, (Ymax - opts.radius)/2 + (setaTamanho/2)/2 );
		  ctx.lineTo(Xmax, (Ymax - opts.radius)/2 - (setaTamanho/2));
	  }
      ctx.lineTo(Xmax, Yzero + opts.radius);
      ctx.quadraticCurveTo(Xmax, Yzero, Xmax - opts.radius, Yzero);
	  // seta top
	   if(lado == 't'){
		  ctx.lineTo((Xmax + opts.radius)/2 + (setaTamanho/2), Yzero );
		  ctx.lineTo((Xmax + opts.radius)/2 - (setaTamanho/2)/2, Yzero - setaTamanho);
		  ctx.lineTo((Xmax + opts.radius )/2 - setaTamanho, Yzero);
	  }
	  if(lado == 'tl'){
		  ctx.lineTo((Xzero + opts.radius + 10) + (setaTamanho/2), Yzero );
		  ctx.lineTo((Xzero + opts.radius + 10) - (setaTamanho/2), Yzero - setaTamanho);
		  ctx.lineTo((Xzero + opts.radius + 10), Yzero);
	  }
	  if(lado == 'tr'){
		  ctx.lineTo((Xmax - opts.radius - 10), Yzero );
		  ctx.lineTo((Xmax - opts.radius - 10) + (setaTamanho/2), Yzero - setaTamanho);
		  ctx.lineTo((Xmax - opts.radius - 10) - (setaTamanho/2), Yzero);
	  }
      ctx.lineTo(Xzero + opts.radius, Yzero);
      ctx.quadraticCurveTo(Xzero, Yzero, Xzero, opts.radius + Yzero);
      ctx.closePath();
      if (this.action) this.action.paint(area);  // for chaining
    },
    shrink: function(area, steps) {
      this.defaultShrink(area, steps);
      this.opts.radius -= steps;
    }
  });
  $.registerLiquidCanvasPlugin({
    name: "fill",
    defaultOpts: { color:"#aaa" },
    paint: function(area) {
      area.ctx.fillStyle = this.opts.color;
      this.action.paint(area);
      area.ctx.fill();
    }
  });
  $.registerLiquidCanvasPlugin({
    name: "triangle",
    defaultOpts: { color:"#aaa" },
    paint: function(area) {
      area.ctx.fillStyle = this.opts.color;
      this.action.paint(area);
      area.ctx.fill();
    }
  });
  $.registerLiquidCanvasPlugin({  // hmmmmmmm, no rotation? no width??? ah patterns!
    name: "image",
    defaultOpts: { url:"http://www.ruzee.com/files/liquid-canvas-image.png" },
    paint: function(area) {
      var image = new Image();
      image.src = this.opts.url;
      image.onload = function() { 
        area.ctx.drawImage(this, 0, 0); 
      };
    }
  });
  $.registerLiquidCanvasPlugin({ 
    name: "gradient",
    defaultOpts: { from: "#ccc", to:"#fff" },
    paint: function(area) {
      var grad = area.ctx.createLinearGradient(0, 0, 0, area.height);
      grad.addColorStop(0, this.opts.from);
      grad.addColorStop(1, this.opts.to);
      area.ctx.fillStyle = grad;
      this.action.paint(area);
      area.ctx.fill();
    }
  });
  $.registerLiquidCanvasPlugin({
    name: "shadow",
    defaultOpts: { width:3, color:'#999', shift:1 },
    paint: function(area) {
      var sw = this.opts.width;
      
      area.ctx.fillStyle = this.opts.color; 
      area.ctx.globalAlpha = 0.3 / sw;
      for (var s = 0; s <= sw; ++s) {
        this.action.paint(area);
        area.ctx.fill();
        this.action.shrink(area, .5);
      }
      area.ctx.globalAlpha = 1;
      area.ctx.translate(0, -this.opts.shift);
    }
  });
  $.registerLiquidCanvasPlugin({
    name: "border",
    defaultOpts: { color:'#f80', width:3 },
    paint: function(area) {
      var bw = this.opts.width;
      area.ctx.strokeStyle = this.opts.color;
      area.ctx.lineWidth = bw;
      this.action.shrink(area, bw / 2);
      this.action.paint(area);
      area.ctx.stroke();
      this.action.shrink(area, bw / 2);
    }
  });

	var Params = {};
	$.fn.removeTollTipRvaz = function() {
		return this.each(function(){
			$tollTip = $('#tollTip-Rvaz-'+$(this).data('tollTip-Rvaz'));
			$tollTip.fadeOut(200,function(){$(this).remove()});
		 });
	}
	$.fn.getTollTipRvazId = function() {
		return 'tollTip-Rvaz-'+$(this).data('tollTip-Rvaz');
	}
	$.fn.tollTipRvaz = function(params) {
		var options = {
			border: {width:0, color:'#fff'},
			gradient: {from:'#fff', to:'#efefef'},
			corner: '10',
			shadow: true,
			pos: 'aa',
			width: 0,
			title: null,
			text: null,
			inlineId: null,
			ajaxContent: null
		}
		
		params 			= $.extend({border:{}, gradient:{}}, params);
		op 				= $.extend({}, options, Params, params);
		op.border 		= $.extend({}, options.border, Params.border, op.border);
		op.gradient 	= $.extend({}, options.gradient, Params.gradient, params.gradient);
		op.corner 		= Number(op.corner) + 3;
	   return this.each(function(){
			$(this).removeTollTipRvaz();
			//initializing variables
			var id			= new Date().getTime();
			$(this).data('tollTip-Rvaz',id);
			var $self 		= $(this);
			$('<div id="tollTip-Rvaz-'+id+'" class="tollTip-Rvaz" ><div id="tollTip-Rvaz-content-'+id+'" class="tollTip-Rvaz-content formulario " '+(op.width != 0 ? 'style="width:'+op.width+'px"' : '')+'></div></div>').prependTo("body");
			var $tollTip    	= $('#tollTip-Rvaz-'+id);
			var $tollTipContent = $('#tollTip-Rvaz-content-'+id);
			//aqui colcoar o conceudo dentro da tollTip
			//op.inlineId.clone().prependTo($tollTipContent);
			$tollTipContent.html(op.inlineId.html());
			
			//if(op.pos == 'auto'){
				positiontollTipRvaz();
			//}
			
			//$tollTip.hide();
			//var $canvas = $('#tollTip-Rvaz-canvas-'+id);
			//$tollTip.fadeIn('normal');
			
			$(window).resize(function(){ positiontollTipRvaz(); });
			$self.attr('Rvaz', op.border.color);
			function hidetollTip($tip){
				$tollTip = $tip || $tollTip;
				$tollTip.fadeOut(200,function(){$(this).remove()});
			}
			function hidetollTipEvent(ev){
				if (ev.target != ev.data.trigger && !isChildOf(ev.data.cal.get(0), ev.target, ev.data.cal.get(0))) {
					if($tollTip){
						$tollTip.remove();
						//$tollTip.fadeOut(200,function(){$(this).remove()});
						$(document).unbind('mousedown', hidetollTipEvent);
					}
				}
			}
			function isChildOf(parentEl, el, container) {
				if (parentEl == el) {
					return true;
				}
				if (parentEl.contains) {
					return parentEl.contains(el);
				}
				if ( parentEl.compareDocumentPosition ) {
					return !!(parentEl.compareDocumentPosition(el) & 16);
				}
				var prEl = el.parentNode;
				while(prEl && prEl != container) {
					if (prEl == parentEl)
						return true;
					prEl = prEl.parentNode;
				}
				return false;
			}
		   function positiontollTipRvaz(){
			   if($tollTipContent && $tollTip.css('display') != 'none'){
					$('#tollTip-Rvaz-canvas-'+id).remove();
					var docTop 			= $(document).scrollTop();
					var docLeft 		= $(document).scrollLeft();
					var docHeight		= $(document).height();
					var docWidth 		= $(document).width();
					var objTop			= $self.offset().top;
					var objLeft			= $self.offset().left;
					var objHeight		= $self.innerHeight();
					var objWidth		= $self.innerWidth();
					var tollTipHeight	= $tollTipContent.innerHeight();
					var tollTipWidth	= $tollTipContent.innerWidth();
					
					var left = objLeft + objWidth/2 - tollTipWidth/2;
					left = left < 0 ? objLeft + objWidth/2 - 10 : left + tollTipWidth > docWidth ? objLeft + objWidth/2 - tollTipWidth + 10: left;
					var top = docTop < objTop - tollTipHeight ? objTop - tollTipHeight : (objTop + objHeight + tollTipHeight < docTop + docHeight ? objTop + objHeight : (objTop - docTop > (docTop + docHeight) - (objTop + objHeight) ? objTop - tollTipHeight : objTop + objHeight));
					var setaTamanho = left == objLeft + objWidth/2 - 10 ? 20 : left == objLeft + objWidth/2 - tollTipWidth + 10 ? 20 : 10;
					
					$tollTipContent.liquidCanvas("["+(op.shadow ? 'shadow' : '')+" gradient{to:#fff; from:#efefef}] => roundedRect{radius:10} ",{lado:top == objTop - tollTipHeight ? 'b' : 't', id:id, setaTamanho:setaTamanho});
					var docTop 			= $(document).scrollTop();
					var docLeft 		= $(document).scrollLeft();
					var docHeight		= $(document).height();
					var docWidth 		= $(document).width();
					var objTop			= $self.offset().top;
					var objLeft			= $self.offset().left;
					var objHeight		= $self.innerHeight();
					var objWidth		= $self.innerWidth();
					var tollTipHeight	= $tollTipContent.innerHeight(); 
					var tollTipWidth	= $tollTipContent.innerWidth();
					$('#tollTip-Rvaz-canvas-'+id).remove(); 
					if(op.pos.charAt(0) == 'l' || op.pos.charAt(0) == 'r'){
						var pos = op.pos.charAt(0);
						var left = pos == 'l' ? objLeft + objWidth : objLeft - tollTipWidth - setaTamanho;
						var top = objTop + ((objHeight/2) - (tollTipHeight/2)) + 5;
					}else{
						var left = objLeft + objWidth/2 - tollTipWidth/2;
						left = left < 0 ? objLeft + objWidth/2 - 10 : left + tollTipWidth > docWidth ? objLeft + objWidth/2 - tollTipWidth + 10: left;
						var top = docTop < objTop - tollTipHeight ? objTop - tollTipHeight : (objTop + objHeight + tollTipHeight < docTop + docHeight ? objTop + objHeight : (objTop - docTop > (docTop + docHeight) - (objTop + objHeight) ? objTop - tollTipHeight : objTop + objHeight));
						var pos = op.pos.charAt(1) != 'a' ? op.pos.charAt(1) : left == objLeft + objWidth/2 - 10 ? 'l' : left == objLeft + objWidth/2 - tollTipWidth + 10 ? 'r' : '';
						//alert(pos);
						pos = (pos.charAt(0) == 'l' || pos.charAt(0) == 'r' || pos.charAt(0) == '') ? op.pos.charAt(0) != 'a' ? op.pos.charAt(0)+pos : top == objTop - tollTipHeight ? 'b'+pos : 't'+pos : pos;
					}
					setaTamanho = pos.length > 1 ? 20 : 10;
					$tollTip.css({top:top+'px', left: op.pos.charAt(1) == 'l' ? objLeft + objWidth/2 - 10 : op.pos.charAt(1) == 'r' ? objLeft + objWidth/2 - tollTipWidth + 10 : left+'px'});
					
					//$tollTipContent.liquidCanvas('[shadow gradient{to:#FFFBCF; from:#FFFEEF;} border{color:#EFCF3B; width:1;}] => roundedRect[shadow gradient{to:#FFFBCF; from:#FFFEEF;} border{color:#EFCF3B; width:1;}] => roundedRect{radius:5}', {lado:'b', id:'1245876521808', setaTamanho:10});
					
					$tollTipContent.liquidCanvas("["+(op.shadow ? 'shadow' : '')+" gradient{to:"+op.gradient.to+"; from:"+op.gradient.from+";} border{color:"+op.border.color+"; width:"+op.border.width+";}] => roundedRect{radius:"+op.corner+"} ",{lado:pos, id:id, setaTamanho:setaTamanho});
					//alert($tollTipContent.length); 
			   }
			}
			
			//verificatr se tem como remover o onmousedown e aplicar um q seja valido para todos
			$(document).bind('mousedown', {cal: $tollTip, trigger: this}, hidetollTipEvent);
	   });
	 }
	$.extend({
		tollTipRvazDefaults: function(plugin) {
		  Params = $.extend({}, plugin);
		}
	});

})(jQuery);