(function($) {
  var has_VML, create_canvas_for, add_shape_to, clear_canvas, shape_from_area,
canvas_style, hex_to_decimal, css3color, is_image_loaded, options_from_area;

has_VML = document.namespaces;
has_canvas = !!document.createElement('canvas').getContext;

if(!(has_canvas || has_VML)) {
  $.fn.maphilight = function() { return this; };
  return;
}

if(has_canvas) {
  hex_to_decimal = function(hex) {
    return Math.max(0, Math.min(parseInt(hex, 16), 255));
  };
  css3color = function(color, opacity) {
    return 'rgba('+hex_to_decimal(color.substr(0,2))+','+hex_to_decimal(color.substr(2,2))+','+hex_to_decimal(color.substr(4,2))+','+opacity+')';
      };
      create_canvas_for = function(img) {
        var c = $('<canvas style="width:'+img.width+'px;height:'+img.height+'px;"></canvas>').get(0);
        c.getContext("2d").clearRect(0, 0, c.width, c.height);
        return c;
      };
      add_shape_to = function(canvas, shape, coords, options, name) {
        var i, context = canvas.getContext('2d');
        context.beginPath();
        if(shape == 'rect') {
          context.rect(coords[0], coords[1], coords[2] - coords[0], coords[3] - coords[1]);
        } else if(shape == 'poly') {
          context.moveTo(coords[0], coords[1]);
          for(i=2; i < coords.length; i+=2) {
            context.lineTo(coords[i], coords[i+1]);
          }
        } else if(shape == 'circ') {
          context.arc(coords[0], coords[1], coords[2], 0, Math.PI * 2, false);
        }
        context.closePath();
        if(options.fill) {
          context.fillStyle = css3color(options.fillColor, options.fillOpacity);
          context.fill();
        }
        if(options.stroke) {
          context.strokeStyle = css3color(options.strokeColor, options.strokeOpacity);
          context.lineWidth = options.strokeWidth;
          context.stroke();
        }
        if(options.fade) {
          $(canvas).css('opacity', 0).animate({opacity: 1}, 100);
        }
      };
      clear_canvas = function(canvas) {
        canvas.getContext('2d').clearRect(0, 0, canvas.width,canvas.height);
      };
} else {   // ie executes this code
  create_canvas_for = function(img) {
    return $('<var style="zoom:1;overflow:hidden;display:block;width:'+img.width+'px;height:'+img.height+'px;"></var>').get(0);
  };
  add_shape_to = function(canvas, shape, coords, options, name) {
    var fill, stroke, opacity, e;
    fill = '<v:fill color="#'+options.fillColor+'" opacity="'+(options.fill ? options.fillOpacity : 0)+'" />';
    stroke = (options.stroke ? 'strokeweight="'+options.strokeWidth+'" stroked="t" strokecolor="#'+options.strokeColor+'"' : 'stroked="f"');
    opacity = '<v:stroke opacity="'+options.strokeOpacity+'"/>';
    if(shape == 'rect') {
      e = $('<v:rect name="'+name+'" filled="t" '+stroke+' style="zoom:1;margin:0;padding:0;display:block;position:absolute;left:'+coords[0]+'px;top:'+coords[1]+'px;width:'+(coords[2] - coords[0])+'px;height:'+(coords[3] - coords[1])+'px;"></v:rect>');
    } else if(shape == 'poly') {
      e = $('<v:shape name="'+name+'" filled="t" '+stroke+' coordorigin="0,0" coordsize="'+canvas.width+','+canvas.height+'" path="m '+coords[0]+','+coords[1]+' l '+coords.join(',')+' x e" style="zoom:1;margin:0;padding:0;display:block;position:absolute;top:0px;left:0px;width:'+canvas.width+'px;height:'+canvas.height+'px;"></v:shape>');
    } else if(shape == 'circ') {
      e = $('<v:oval name="'+name+'" filled="t" '+stroke+' style="zoom:1;margin:0;padding:0;display:block;position:absolute;left:'+(coords[0] - coords[2])+'px;top:'+(coords[1] - coords[2])+'px;width:'+(coords[2]*2)+'px;height:'+(coords[2]*2)+'px;"></v:oval>');
    }
    e.get(0).innerHTML = fill+opacity;
    $(canvas).append(e);
  };
  clear_canvas = function(canvas) {
    $(canvas).find('[name=highlighted]').remove();
  };
}

shape_from_area = function(area) {
  var i, coords = area.getAttribute('coords').split(',');
  for (i=0; i < coords.length; i++) { coords[i] = parseFloat(coords[i]); }
  return [area.getAttribute('shape').toLowerCase().substr(0,4), coords];
};

options_from_area = function(area, options) {
  var $area = $(area);
  return $.extend({}, options, $.metadata ? $area.metadata() : false, $area.data('maphilight'));
};

is_image_loaded = function(img) {
  if(!img.complete) { return false; } // IE
  if(typeof img.naturalWidth != "undefined" && img.naturalWidth == 0) { return false; } // Others
  return true;
};

canvas_style = {
  position: 'absolute',
  left: 0,
  top: 0,
  padding: 0,
  border: 0
};

var ie_hax_done = false;
$.fn.maphilight = function(opts) {
  opts = $.extend({}, $.fn.maphilight.defaults, opts);

  if(!has_canvas && $.browser.msie && !ie_hax_done) {
    document.namespaces.add("v", "urn:schemas-microsoft-com:vml");
    var style = document.createStyleSheet();
    var shapes = ['shape','rect', 'oval', 'circ', 'fill', 'stroke', 'imagedata', 'group','textbox'];
    $.each(shapes,
        function() {
          style.addRule('v\\:' + this, "behavior: url(#default#VML); antialias:true");
        }
        );
    ie_hax_done = true;
  }

  return this.each(function() {
    var img, wrap, options, map, canvas, canvas_always, mouseover, highlighted_shape, usemap;
    img = $(this);

    if(!is_image_loaded(this)) {
      // If the image isn't fully loaded, this won't work right.  Try again later.
      return window.setTimeout(function() {
        img.maphilight(opts);
      }, 200);
    }

    options = $.extend({}, opts, $.metadata ? img.metadata() : false, img.data('maphilight'));

    // jQuery bug with Opera, results in full-url#usemap being returned from jQuery's attr.
    // So use raw getAttribute instead.
    usemap = img.get(0).getAttribute('usemap');

    map = $('map[name="'+usemap.substr(1)+'"]');

    if(!(img.is('img') && usemap && map.size() > 0)) {
      return;
    }

    if(img.hasClass('maphilighted')) {
      // We're redrawing an old map, probably to pick up changes to the options.
      // Just clear out all the old stuff.
      var wrapper = img.parent();
      img.insertBefore(wrapper);
      wrapper.remove();
      $(map).unbind('.maphilight').find('area[coords]').unbind('.maphilight');
    }

    wrap = $('<div></div>').css({
      display:'block',
         background:'url("'+this.src+'")',
         position:'relative',
         padding:0,
         width:this.width,
         height:this.height
    });
    if(options.wrapClass) {
      if(options.wrapClass === true) {
        wrap.addClass($(this).attr('class'));
      } else {
        wrap.addClass(options.wrapClass);
      }
    }
    img.before(wrap).css('opacity', 0).css(canvas_style).remove();
    if($.browser.msie) { img.css('filter', 'Alpha(opacity=0)'); }
    wrap.append(img);

    canvas = create_canvas_for(this);
    $(canvas).css(canvas_style);
    canvas.height = this.height;
    canvas.width = this.width;

    mouseover = function(e) {
      var shape, area_options;
      area_options = options_from_area(this, options);
      if(
          !area_options.neverOn
          &&
          !area_options.alwaysOn
        ) {
        shape = shape_from_area(this);
        add_shape_to(canvas, shape[0], shape[1], area_options, "highlighted");
        if(area_options.groupBy) {
          var areas;
          // two ways groupBy might work; attribute and selector
          if(/^[a-zA-Z][-a-zA-Z]+$/.test(area_options.groupBy)) {
            areas = map.find('area['+area_options.groupBy+'="'+$(this).attr(area_options.groupBy)+'"]')
          } else {
            areas = map.find(area_options.groupBy);
          }
          var first = this;
          areas.each(function() {
            if(this != first) {
              var subarea_options = options_from_area(this, options);
              if(!subarea_options.neverOn && !subarea_options.alwaysOn) {
                var shape = shape_from_area(this);
                add_shape_to(canvas, shape[0], shape[1], subarea_options, "highlighted");
              }
            }
          });
        }
        // workaround for IE7, IE8 not rendering the final rectangle in a group
        if(!has_canvas) {
          $(canvas).append('<v:rect></v:rect>');
        }
      }
    }

    $(map).bind('alwaysOn.maphilight', function() {
      // Check for areas with alwaysOn set. These are added to a *second* canvas,
      // which will get around flickering during fading.
      if(canvas_always) {
        clear_canvas(canvas_always)
      }
      if(!has_canvas) {
        $(canvas).empty();
      }
      $(map).find('area[coords]').each(function() {
        var shape, area_options;
        area_options = options_from_area(this, options);
        if(area_options.alwaysOn) {
          if(!canvas_always && has_canvas) {
            canvas_always = create_canvas_for(img.get());
            $(canvas_always).css(canvas_style);
            canvas_always.width = img.width();
            canvas_always.height = img.height();
            img.before(canvas_always);
          }
          area_options.fade = area_options.alwaysOnFade; // alwaysOn shouldn't fade in initially
          shape = shape_from_area(this);
          if (has_canvas) {
            add_shape_to(canvas_always, shape[0], shape[1], area_options, "");
          } else {
            add_shape_to(canvas, shape[0], shape[1], area_options, "");
          }
        }
      });
    });

    $(map).trigger('alwaysOn.maphilight').find('area[coords]')
      .bind('mouseover.maphilight', mouseover)
      .bind('mouseout.maphilight', function(e) { clear_canvas(canvas); });;

    img.before(canvas); // if we put this after, the mouseover events wouldn't fire.

    img.addClass('maphilighted');
  });
};
$.fn.maphilight.defaults = {
  fill: true,
  fillColor: '000000',
  fillOpacity: 0.2,
  stroke: true,
  strokeColor: 'ff0000',
  strokeOpacity: 1,
  strokeWidth: 1,
  fade: true,
  alwaysOn: false,
  neverOn: false,
  groupBy: false,
  wrapClass: true
};
})(jQuery);

