/* Lightmapper/v, version 1.0
 *
 * (C) 2007 Dmitriy Khudorozhkov (mailto:kh_dmitry2001@mail.ru)
 *
 * This software is provided "as-is", without any express or implied warranty.
 * In no event will the author be held liable for any damages arising from the
 * use of this software.
 *
 * Permission is granted to anyone to use this software for any purpose,
 * including commercial applications, and to alter it and redistribute it
 * freely, subject to the following restrictions:
 *
 * 1. The origin of this software must not be misrepresented; you must not
 *    claim that you wrote the original software. If you use this software
 *    in a product, an acknowledgment in the product documentation would be
 *    appreciated but is not required.
 *
 * 2. Altered source versions must be plainly marked as such, and must not be
 *    misrepresented as being the original software.
 *
 * 3. This notice may not be removed or altered from any source distribution.
 *
 */

function LightmapperV(imgId,     // id of the IMAGE element the map is bound to,
                      mouseOver, // global onMouseOver handler,
                      mouseOut,  // global onMouseOut handler
                      bindings,  // array of arrays of area/color/handler bindings:
                      iter)      //
                                 // [area_id, color, x_displacement, y_displacement,
                                 //   on_mouse_over, on_mouse_out, linked_area_ids...]
                                 //
                                 // where:
                                 //
                                 // area_id         - id of the area,
                                 //
                                 // color           - colors (in #xxx or #xxxxxx format) that will be used
                                 //                   to highlight the area,
                                 // 
                                 // x_displacement  - horiz. displacement (found experimentally) required for
                                 //                   correct alignment of mouse-over image and the original map,
                                 //
                                 // y_displacement  - vertical displacement,
                                 // 
                                 // on_mouse_over   - onMouseOver handler for current (area_id) area
                                 //                   (replaces the global handler),
                                 //
                                 // on_mouse_out    - onMouseOut handler for current (area_id) area
                                 //                   (replaces the global handler),
                                 //
                                 // linked_area_ids - ids, comma separated, of the linked areas
                                 //                   (= pop up with the current area).
{
  this.imgId = imgId;
  
  this.iter = 1;
  
  if (iter)
    this.iter = iter;

  this.mouseOver = mouseOver || null;
  this.mouseOut  = mouseOut  || null;

  this.binds = [];

  for(var i = 0, l = bindings.length; i < l; i++)
  {
    var elem = this.binds[i] = [];
    var ref  = bindings[i];

    elem["area"] = ref[0];
    elem["colr"] = ref[1] || "#0F0";
    elem["xdsp"] = ref[2] || null;
    elem["ydsp"] = ref[3] || null;
    elem["musi"] = ref[4] || null;
    elem["muso"] = ref[5] || null;

    elem["link"] = [];
    for(var j = 6; j < bindings[i].length; j++)
      elem["link"][elem["link"].length] = bindings[i][j];
  }

  this._construct();
}

LightmapperV.prototype = {

  // CONSTRUCT: Constructor for LightMapperV class
  _construct: function()
  {
    var ct  = document.getElementById(this.imgId), _ct_ = ct.parentNode;
    var div = this.canvas = document.createElement("DIV");

    this._reposition(this);

    // Add the overlay div to the image map's container div
    _ct_.appendChild(div);

    // Set up the vector drawing object for drawing the overlay layer polygons
    var jvg = new VectorGraphics(div);
    jvg.SetOpacity(0);
    jvg.SetStrokeWidth(0);

    // Create floating DIVs (overlay polygons)
    for(var i = 0, l = this.binds.length; i < l; i++)
    {
      var elem = this.binds[i], area = document.getElementById(elem["area"]);

      // Convert image map area coords to area coords pair of X/Y values
      var coords = area.coords.split(","), coords2 = [];

      var m = coords.length;
      while(m)
      {
        coords2[m / 2 - 1] = [+coords[--m - 1] + elem["xdsp"],
                              +coords[--m + 1] + elem["ydsp"]];
      }

      // Set the specified fill/outline color
      jvg.SetFillColor(elem["colr"]);
      jvg.SetStrokeColor(elem["colr"]);

      // Create the polygon (and add to the overlay layer)
      var polygon = this.binds[i].overlay = jvg.Polygon(coords2);

      polygon.area   = elem["area"];    // Image map area reference
      polygon.links  = [];
      polygon.parent = this;
      
      // Show the polygon
      this._highlight(polygon, 0.5)

      // Setup fade effect on mouseover and mouseout (and user specified mouseover/mouseout events)
      var mi = elem["musi"] || this.mouseOver;
      var mo = elem["muso"] || this.mouseOut;

      this._setup_event(polygon, "mouseover", this._callLater(this._fade, polygon, 0.7, 0.5, 0.1, mi));
      this._setup_event(polygon, "mouseout",  this._callLater(this._fade, polygon, 0.5, 0.5, 0.1, mo));

      // If the image map area has a link specified, attach function to
      //  "GO" to that URL on mouse click (mousedown)
      if(area.href)
      {
        function go(href) { return function() { window.location.href = href; } }

        this._setup_event(polygon, "mousedown",  go(area.href));
      }
    }

    var bl = this.binds.length;
    for(var i = 0; i < l; i++)
    {
      var obj   = this.binds[i].overlay;
      var links = this.binds[i]["link"], ll = links.length;

      for(var k = 0; k < ll; k++)
      {
        for(var j = 0; j < bl; j++)
        {
          var elem = this.binds[j].overlay;

          if(links[k] == elem["area"])
          {
            obj.links[obj.links.length] = elem;
            break;
          }
        }
      }
    }

    this._setup_event(window, "resize",  this._callLater(this._reposition, this));
  },

  // REPOSITION: Reposition the overlay depending on browser window size,
  //             image size (and browser type - IE, Mozilla, etc.).
  _reposition: function(obj)
  {
    var ct  = document.getElementById(obj.imgId);
    var div = obj.canvas, ds = div.style;

    // Retrieve the position of the target image:
    var initX = 0, initY = 0, ct_ = ct;

    if(ct_.offsetParent)
    {
      while (ct_.offsetParent)
      {
        initX += ct_.offsetLeft;
        initY += ct_.offsetTop;

        ct_ = ct_.offsetParent;
      }
    }
    else if (ct_.x)
    {
      initX += ct_.x;
      initY += ct_.y;
    }

    // ---------------------------------------------------------------------------------------
    // Position the overlay [needed for (X)HTML doctype of transitional]
    ds.position = "absolute";
    ds.display = "block";
    ds.margin = "0px";
    ds.padding = "0px";
    ds.top = "1px";
    ds.left = "1px";
    
    // Check if browser is IE. If so, then adjust position (for margin/padding and position
    //  layout rendering differences).
    var browser = navigator.appName
    if (browser == "Microsoft Internet Explorer") {
        ds.marginTop = "70px";
        ds.marginLeft = "36px";
    }
    // ---------------------------------------------------------------------------------------
  },

  // SETUP_EVENT: Attaches an function (handler) to an element (elem) for
  //              specified event (eventType)
  _setup_event: function(elem, eventType, handler)
  {    
    return (elem.attachEvent ? elem.attachEvent("on" + eventType, handler) :
      ((elem.addEventListener) ? elem.addEventListener(eventType, handler, false) : null));
  },

  // CALLLATER: Builds a "reference" to a specified function and it's parameters
  //            for later reference.
  _callLater: function(func, param1, param2, param3, param4, param5)
  {
    return function() { func(param1, param2, param3, param4, param5); };
  },

  // GETSETOPACITY: If opacity is specified, will set the polygon's opacity to it and
  //                return the value. If not, will just return the current opacity.
  _getSetOpacity: function(polygon, opacity)
  {
    if(polygon.hasAttributes && polygon.hasAttributes("fill-opacity"))
    {
      if(opacity != undefined)
      {
        polygon.setAttribute("fill-opacity", opacity);
        polygon.setAttribute("stroke-opacity", opacity);

        return opacity;
      }
      else
        return polygon.getAttribute("fill-opacity");
    }
    else if(polygon.tagName.toLowerCase() == "shape")
    {
      var c = polygon.childNodes.length;

      for(var i = 0; i < c; i++)
      {
        var child = polygon.childNodes[i], tag = child.tagName.toLowerCase();

        if(opacity != undefined)
        {
          var f = false, s = false;

          if(tag == "fill")
          {
            child.opacity = opacity;
            f = true;
          }
          if(tag == "stroke")
          {
            child.opacity = opacity;
            s = true;
          }

          if(f && s) return opacity;
        }
        else
          if((tag == "fill") || (tag == "stroke"))
            return child.opacity;
      }
    }

    return 1;
  },

  // FADE: Depending on what destination opacity is set to, fade will change the opacity
  //       of a polygon (obj) by a rate of delta. The speed at which one fade level changes
  //       to the next is determined by rate.
  _fade: function(obj,        // The object (polygon) with which to fade
                  destOp,     // Destination Opacity (1: max, 0: min)
                  rate,       // Timer rate to wait until next opactiy level fade
                  delta,      // How much to increase/decrease opacity by
                  callback)
  {
    if(obj.timer) clearTimeout(obj.timer);

    var proto = LightmapperV.prototype;

    var curOp = parseFloat(proto._getSetOpacity(obj));  // current opacity for polygon
    var direction = (curOp <= destOp) ? 1 : -1;         // make more [-1] or less [1] transparent (fill/outline color)

    var links = obj.links, bindings = obj.parent.binds;
    var bl = bindings.length, ll = links.length;

    if((destOp > curOp) && (curOp == 0))
    {
      for(var i = 0; i < bl; i++)
      {
        var elem = bindings[i].overlay;                 // overlay polygon

        if(elem != obj)
        {
          for(var j = 0; j < ll; j++)
            if(links[j] == elem)
              break;
			  
          if(j == ll) proto._fade(elem, 0, 0.5, 0.1);
        }
      }
    }

    delta  = Math.min(direction * (destOp - curOp), delta); // find the min of [what's left to change by] or [the delta]
    curOp += direction * delta;                             // calculate the new opacity

    curOp = Math.round(curOp * 10) / 10;                    // make sure new opacity is rounded to nearest 10th's place

    proto._getSetOpacity(obj, curOp);                       // set the new opacity for the current polygon

    for(var j = 0; j < ll; j++)
      proto._getSetOpacity(links[j], curOp);                // do the same for the linked bindings

    if(curOp != destOp)
      obj.timer = setTimeout(function() { proto._fade(obj, destOp, rate, delta, callback); }, rate);    // fade to next opacity level
    else
    {
      if(callback)
        callback(obj.area);
    }
  },
  
  _highlight: function(obj,        // The object (polygon) with which to hightlight
                       opacity)    // Opacity (1: No transparency, 0: Completely transparent)
  {
    var proto = LightmapperV.prototype;
    
    proto._getSetOpacity(obj, opacity);     // set the new opacity for the current polygon
  }
};