ColorPicker = {

  // P U B L I C

  // Configuration hash
  // Edit or override these before calling ColorPicker.initialize()
  configHash : {

    // Path to color picker images
    'imagePath' : '/images/colorpicker/',

    // Default color
    'defaultColor' : '#000000',

    // Main div id
    'MainID' : 'colorpickerMain',

    // Saturation & Value area div id
    'SVID' : 'colorpickerSVBackground',
    
    // Saturation & Value area width
    'SVWidth' : 170,
    
    // Saturation & Value area height
    'SVHeight' : 170,
    
    // Saturation & Value area border width
    'SVBorderWidth' : 1,
    
    // Saturation & Value area border color
    'SVBorderColor' : '#333333',
    
    // Saturation & Value area background image
    'SVBackgroundImage' : 'SVBackground.png',

    // Saturation & Value cursor div id
    'SVCursorID' : 'colorpickerSVCursor',
    
    // Saturation & Value cursor width
    'SVCursorWidth' : 9,
    
    // Saturation & Value cursor height
    'SVCursorHeight' : 9,
    
    // Saturation & Value cursor image
    'SVCursorImage' : 'SVCursor.gif',

    // Hue area div ID
    'HID' : 'colorpickerHBackground',
    
    // Hue area width
    'HWidth' : 20,
    
    // Hue area heigth
    'HHeight' : 170,
    
    // Hue area border width
    'HBorderWidth' : 1,

    // Hue area border color
    'HBorderColor' : '#333333',

    // Hue area left offset
    'HOffset' : 10,

    // Hue cursor div id
    'HCursorID' : 'colorpickerHCursor',

    // Hue cursor width
    'HCursorWidth' : 34,

    // Hue cursor height
    'HCursorHeight' : 9,

    // Hue cursor image
    'HCursorImage' : 'HCursor.gif'
  },

  // Set (and update) the current color
  setColor : function(color, skipCallHandler) {
    ColorPicker.hsv = ColorPicker.hex2hsv(color);
    var h = ColorPicker.hsv[0];
    var s = ColorPicker.hsv[1];
    var v = ColorPicker.hsv[2];
    ColorPicker.object(ColorPicker.configHash.SVID).style.backgroundColor = '#' + ColorPicker.hsv2hex([h, 1, 1]);
    ColorPicker.object(ColorPicker.configHash.HCursorID).style.top =
      Math.round((1 - h) * ColorPicker.configHash.HHeight - ColorPicker.configHash.HCursorHeight / 2) + 'px';
    ColorPicker.object(ColorPicker.configHash.SVCursorID).style.left =
      Math.round(s * ColorPicker.configHash.SVWidth - ColorPicker.configHash.SVCursorWidth / 2) + 'px';
    ColorPicker.object(ColorPicker.configHash.SVCursorID).style.top =
      Math.round((1 - v) * ColorPicker.configHash.SVHeight - ColorPicker.configHash.SVCursorHeight / 2) + 'px';
    if (!skipCallHandler) {
      ColorPicker.handlerOnColorUpdate();
    }
  },

  // Get color in hex format
  getColor : function() {
    return '#' + ColorPicker.hsv2hex(ColorPicker.hsv);
  },

  // Color picker initialization
  initialize : function() {
    try {

      // Main DIV
      var o = ColorPicker.object(ColorPicker.configHash.MainID);
      o.style.position = 'relative';
      o.style.cursor = 'hand';
      o.style.cursor = 'pointer';
      o.innerHTML = '<div id="' + ColorPicker.configHash.SVID + '">' +
                    '<div id="' + ColorPicker.configHash.SVCursorID + '">&nbsp;</div></div>' +
                    '<div id="' + ColorPicker.configHash.HID + '">' +
                    '<div id="' + ColorPicker.configHash.HCursorID + '">&nbsp;</div></div>';

      // Saturation & Value Background
      o = ColorPicker.object(ColorPicker.configHash.SVID);
      o.onmousedown = ColorPicker.eventHandler(ColorPicker.startDrag, o);
      o.style.position = 'relative';
      o.style.cursor = 'crosshair';
      if (navigator.userAgent.indexOf('MSIE') == -1) {
        o.style.backgroundImage = 'url("' + ColorPicker.configHash.imagePath + ColorPicker.configHash.SVBackgroundImage + '")';
      } else {
        o.style.filter = 'progid:DXImageTransform.Microsoft.AlphaImageLoader(src="' +
               ColorPicker.configHash.imagePath +
               ColorPicker.configHash.SVBackgroundImage + '", sizingMethod="scale")';
      }
      o.style.backgroundColor = 'green';
      o.style.border = ColorPicker.configHash.SVBorderWidth + 'px solid ' + ColorPicker.configHash.SVBorderColor;
      o.style.height = ColorPicker.configHash.SVHeight + 'px';
      o.style.width = ColorPicker.configHash.SVWidth + 'px';
      o.style.MozUserSelect = o.style.KhtmlUserSelect = o.style.userSelect = 'none';

      // Saturation & Value Cursor
      o = ColorPicker.object(ColorPicker.configHash.SVCursorID);
      o.onmousedown = ColorPicker.eventHandler(ColorPicker.startDrag, o);
      o.style.position = 'absolute';
      o.style.backgroundImage = 'url("' + ColorPicker.configHash.imagePath + ColorPicker.configHash.SVCursorImage + '")';
      o.style.top = (-ColorPicker.configHash.SVCursorHeight / 2) + 'px';
      o.style.left = (-ColorPicker.configHash.SVCursorWidth / 2) + 'px';
      o.style.height = ColorPicker.configHash.SVCursorHeight + 'px';
      o.style.width = ColorPicker.configHash.SVCursorWidth + 'px';
      o.style.lineHeight = '1px';

      // Hue Background
      o = ColorPicker.object(ColorPicker.configHash.HID);
      o.onmousedown = ColorPicker.eventHandler(ColorPicker.startDrag, o);
      o.style.position = 'absolute';
      o.style.cursor = 'crosshair';
      o.style.border = ColorPicker.configHash.HBorderWidth + 'px solid ' + ColorPicker.configHash.HBorderColor;
      o.style.top = '0px';
      o.style.left = (ColorPicker.configHash.SVWidth + ColorPicker.configHash.HOffset) + 'px';
      o.style.height = ColorPicker.configHash.HHeight + 'px';
      o.style.width = ColorPicker.configHash.HWidth + 'px';
      o.style.MozUserSelect = o.style.KhtmlUserSelect = o.style.userSelect = 'none';
      var s = '';
      for (var i = ColorPicker.configHash.HHeight - 1; i >= 0; i--) {
        s += '<div style="background-color: #' +
             ColorPicker.hsv2hex([i / ColorPicker.configHash.HHeight, 1, 1]) +
             '; height: 1px;"><!-- Hue --></div>';
      }
      o.innerHTML += s;

      // Hue Cursor
      o = ColorPicker.object(ColorPicker.configHash.HCursorID);
      o.onmousedown = ColorPicker.eventHandler(ColorPicker.startDrag, o);
      o.style.position = 'absolute';
      o.style.backgroundImage = 'url("' + ColorPicker.configHash.imagePath + ColorPicker.configHash.HCursorImage + '")';
      o.style.top = Math.round(-ColorPicker.configHash.HCursorHeight / 2) + 'px';
      o.style.left = Math.round((ColorPicker.configHash.HWidth - ColorPicker.configHash.HCursorWidth) / 2) + 'px';
      o.style.height = ColorPicker.configHash.HCursorHeight + 'px';
      o.style.width = ColorPicker.configHash.HCursorWidth + 'px';
      o.style.lineHeight = '1px';

    } catch(e) {
      alert("Color Picker initialization failed");
    }
    
    ColorPicker.setColor(ColorPicker.configHash.defaultColor);
  },

  // Handler on color update - redefine this
  handlerOnColorUpdate : function() {
  },

  // P R I V A T E
  
  hsv : [],
  dragInfo : {},

  eventHandler : function(handler, object) {
    return function(event) {
      if (!event) {
        event = window.event;
      }
      if (event) {
        event.cancelBubble = true;
        handler(object, event);
      }
      return false;
    }
  },

  getElementPosition : function(elem) {
    if (typeof(elem.offsetLeft) != 'undefined') {
      var left = 0;
      var top = 0;
      var current_elem = elem;
      while (current_elem) {
        left += current_elem.offsetLeft;
        top += current_elem.offsetTop;
        current_elem = current_elem.offsetParent;
      }
      return {'x' : left, 'y' : top};
    }
    return {'x' : 0, 'y' : 0};
  },

  getElementDimensions : function(elem) {
    if (typeof(elem.offsetHeight) != 'undefined') {
      return {'w' : elem.offsetWidth, 'h' : elem.offsetHeight};
    }
    return {'w' : 0, 'h' : 0};
  },

  getPageScroll : function() {
    if (typeof(window.pageXOffset) != 'undefined') {
      return {'x' : window.pageXOffset, 'y' : window.pageYOffset};
    }
    if (document.body && typeof(document.body.scrollLeft) != 'undefined') {
      return {'x' : document.body.scrollLeft, 'y' : document.body.scrollTop};
    }
    if (document.documentElement && typeof(document.documentElement.scrollLeft) != 'undefined') {
      return {'x' : document.documentElement.scrollLeft, 'y' : document.documentElement.scrollTop};
    }
    return {'x' : 0, 'y' : 0};
  },

  getEventPosition : function(event) {
    if (event && event.pageX) {
      return {'x' : event.pageX, 'y' : event.pageY};
    }
    if (event && event.clientX) {
      var pageScroll = ColorPicker.getPageScroll();
      return {'x' : event.clientX + pageScroll.x, 'y' : event.clientY + pageScroll.y};
    }
    return {'x' : 0, 'y' : 0};
  },

  object : function(id) {
    if (document.getElementById) {
      return document.getElementById(id);
    }
    return null;
  },

  resetDrag : function() {
    document.onmousemove = ColorPicker.dragInfo.onMouseMove;
    document.onmouseup = ColorPicker.dragInfo.onMouseUp;
    document.onselectstart = ColorPicker.dragInfo.onSelectStart;
    ColorPicker.dragInfo = {
      'onMouseMove' : null,
      'onMouseUp' : null,
      'onSelectStart' : null,
      'object' : null,
      'objectOffset' : null,
      'objectDimensions' : null
    };
  },

  startDrag : function(object, event) {
    var cursorObject, bgObject,
        objectDimensions, objectOffset, borderWidth,
        boundingRect;
    borderWidth = 0;
    switch(object.id) {
      case ColorPicker.configHash.SVID:
      case ColorPicker.configHash.SVCursorID:
        cursorObject = ColorPicker.object(ColorPicker.configHash.SVCursorID);
        bgObject = ColorPicker.object(ColorPicker.configHash.SVID);
        boundingRect = {
          'x' : 0,
          'y' : 0,
          'w' : ColorPicker.configHash.SVWidth,
          'h' : ColorPicker.configHash.SVHeight
        };
        borderWidth = ColorPicker.configHash.SVBorderWidth;
        break;
      case 'colorpickerHBackground':
      case 'colorpickerHCursor':
        cursorObject = ColorPicker.object(ColorPicker.configHash.HCursorID);
        bgObject = ColorPicker.object(ColorPicker.configHash.HID);
        boundingRect = {
          'x' : ColorPicker.configHash.HWidth / 2,
          'y' : 0,
          'w' : 0,
          'h' : ColorPicker.configHash.HHeight
        };
        borderWidth = ColorPicker.configHash.HBorderWidth;
        break;
    }
    objectDimensions = ColorPicker.getElementDimensions(cursorObject);
    objectOffset = ColorPicker.getElementPosition(bgObject);
    objectOffset.x += borderWidth;
    objectOffset.y += borderWidth;
    
    ColorPicker.dragInfo = {
      'onMouseMove' : document.onmousemove,
      'onMouseUp' : document.onmouseup,
      'onSelectStart' : document.onselectstart,
      'object' : cursorObject,
      'objectDimensions' : objectDimensions,
      'objectOffset' : objectOffset,
      'boundingRect' : boundingRect
    }
    document.onmousemove = ColorPicker.eventHandler(ColorPicker.drag, document);
    document.onmouseup = ColorPicker.resetDrag;
    document.onselectstart = function() {return false;};
    
    ColorPicker.drag(null, event);
  },

  drag : function(object, event) {
    if (!ColorPicker.dragInfo.object) {
      return;
    }
    var eventPosition = ColorPicker.getEventPosition(event);
    var x = eventPosition.x - ColorPicker.dragInfo.objectOffset.x;
    var y = eventPosition.y - ColorPicker.dragInfo.objectOffset.y;
    x = Math.max(ColorPicker.dragInfo.boundingRect.x, Math.min(
        ColorPicker.dragInfo.boundingRect.x + ColorPicker.dragInfo.boundingRect.w, x));
    y = Math.max(ColorPicker.dragInfo.boundingRect.y, Math.min(
        ColorPicker.dragInfo.boundingRect.y + ColorPicker.dragInfo.boundingRect.h, y));
    ColorPicker.dragInfo.object.style.left = Math.round(x - ColorPicker.dragInfo.objectDimensions.w / 2) + 'px';
    ColorPicker.dragInfo.object.style.top = Math.round(y - ColorPicker.dragInfo.objectDimensions.h / 2) + 'px';
    if (ColorPicker.dragInfo.object.id == ColorPicker.configHash.HCursorID) {
      ColorPicker.hsv[0] = 1 - y / ColorPicker.configHash.HHeight;
      ColorPicker.object(ColorPicker.configHash.SVID).style.backgroundColor = '#' + ColorPicker.hsv2hex([ColorPicker.hsv[0], 1, 1]);
    }
    if (ColorPicker.dragInfo.object.id == ColorPicker.configHash.SVCursorID) {
      ColorPicker.hsv[1] = x / ColorPicker.configHash.SVWidth;
      ColorPicker.hsv[2] = 1 - y / ColorPicker.configHash.SVHeight;
    }
    ColorPicker.handlerOnColorUpdate();
  },

  dec2hex : function(d) {
    var v = Math.round(Math.min(Math.max(0, d), 255));
    var s = "0123456789ABCDEF";
    return s.charAt((v - v % 16) / 16) +
           s.charAt(v % 16);
  },

  hex2rgb : function(hex) {
    return [
      parseInt(hex.substr(0, 2), 16),
      parseInt(hex.substr(2, 2), 16),
      parseInt(hex.substr(4, 2), 16)
    ];
  },

  rgb2hex : function(rgb) {
    var r = rgb[0];
    var g = rgb[1];
    var b = rgb[2];

    return ColorPicker.dec2hex(r) +
           ColorPicker.dec2hex(g) +
           ColorPicker.dec2hex(b);
  },

  hsv2hex : function(hsv) {
    return ColorPicker.rgb2hex(ColorPicker.hsv2rgb(hsv));
  },

  hex2hsv : function(hex) {
    return ColorPicker.rgb2hsv(ColorPicker.hex2rgb(hex.replace(/^#/, '')));
  },

  // from: http://easyrgb.com/math.php?MATH=M20#text20
  rgb2hsv : function(rgb) {
    var r = rgb[0] / 255;
    var g = rgb[1] / 255;
    var b = rgb[2] / 255;

    var min = Math.min(r, g, b);
    var max = Math.max(r, g, b);
    var delta = max - min;

    var h = 0;
    var s = 0;
    var v = max;

    if (delta == 0) {
      h = 0;
      s = 0;
    } else {
      s = delta / max;

      var dr = (((max - r) / 6) + (delta / 2)) / delta;
      var dg = (((max - g) / 6) + (delta / 2)) / delta;
      var db = (((max - b) / 6) + (delta / 2)) / delta;

      if (r == max) {
        h = db - dg;
      } else if (g == max) {
        h = 1/3 + dr - db;
      } else if (b == max) {
        h = 2/3 + dg - dr;
      }

      if (h < 0) {
        h += 1;
      }
      
      if (h > 1) {
        h -= 1;
      }
    }

    return [h, s, v];
  },

  // from: http://easyrgb.com/math.php?MATH=M21#text21
  hsv2rgb : function(hsv){
    var h = hsv[0];
    var s = hsv[1];
    var v = hsv[2];

    var r;
    var g;
    var b;
    
    if (s == 0) {
      r = v * 255;
      g = v * 255;
      b = v * 255;
    } else {
      h = h * 6;
      if (h == 6) {
        h = 0;
      }
      var i = Math.floor(h);
      var v1 = v * (1 - s);
      var v2 = v * (1 - s * (h - i));
      var v3 = v * (1 - s * (1 - (h - i)));

      switch (i) {
        case 0:
          r = v;
          g = v3;
          b = v1;
          break;
        case 1:
          r = v2;
          g = v;
          b = v1;
          break;
        case 2:
          r = v1;
          g = v;
          b = v3;
          break;
        case 3:
          r = v1;
          g = v2;
          b = v;
          break;
        case 4:
          r = v3;
          g = v1;
          b = v;
          break;
        default:
          r = v;
          g = v1;
          b = v2;
          break;
      }

      r = Math.round(r * 255);
      g = Math.round(g * 255);
      b = Math.round(b * 255);
    }
    
    return [r, g, b];
  }

};

