/**
 * Farbtastic Color Picker X
 * ﾂｩ 2008 Steven Wittens
 * 2010 NK
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */
(function($) {
  $.fn.farbtastic = function (link, callback) {
    $.farbtastic(this, link, callback);
    return this;
  };
  
  $.farbtastic = function (container, link, callback) {
    var container = $(container);
    if (! container.data('farbtastic'))
      container.data('farbtastic', new farbtastic(container, link, callback));
    return container.data('farbtastic');
  }

  farbtastic.prototype = {$: 'farbtastic'
    // Dimensions
    , radius: 84
    , square: 100
    , width:  194

    , dragging: false
  
    /* Various color utility functions */
    , pack: function (rgb) {
      var r = Math.round(rgb[0] * 255);
      var g = Math.round(rgb[1] * 255);
      var b = Math.round(rgb[2] * 255);
      return '#' + (r < 16 ? '0' : '') + r.toString(16) +
             (g < 16 ? '0' : '') + g.toString(16) +
             (b < 16 ? '0' : '') + b.toString(16);
    }

    , unpack: function (color) {
      if (! color) return;
      if (color.length == 7) {
        return [parseInt('0x' + color.substring(1, 3)) / 255,
          parseInt('0x' + color.substring(3, 5)) / 255,
          parseInt('0x' + color.substring(5, 7)) / 255];
      }
      else if (color.length == 4) {
        return [parseInt('0x' + color.substring(1, 2)) / 15,
          parseInt('0x' + color.substring(2, 3)) / 15,
          parseInt('0x' + color.substring(3, 4)) / 15];
      }
    }
  
    , HSLToRGB: function (hsl) {
      var m1, m2, r, g, b;
      var h = hsl[0], s = hsl[1], l = hsl[2];
      m2 = (l <= 0.5) ? l * (s + 1) : l + s - l*s;
      m1 = l * 2 - m2;
      return [this.hueToRGB(m1, m2, h+0.33333),
          this.hueToRGB(m1, m2, h),
          this.hueToRGB(m1, m2, h-0.33333)];
    }
  
    , hueToRGB: function (m1, m2, h) {
      h = (h < 0) ? h + 1 : ((h > 1) ? h - 1 : h);
      if (h * 6 < 1) return m1 + (m2 - m1) * h * 6;
      if (h * 2 < 1) return m2;
      if (h * 3 < 2) return m1 + (m2 - m1) * (0.66666 - h) * 6;
      return m1;
    }
  
    , RGBToHSL: function (rgb) {
      var min, max, delta, h, s, l;
      var r = rgb[0], g = rgb[1], b = rgb[2];
      min = Math.min(r, Math.min(g, b));
      max = Math.max(r, Math.max(g, b));
      delta = max - min;
      l = (min + max) / 2;
      s = 0;
      if (l > 0 && l < 1) {
        s = delta / (l < 0.5 ? (2 * l) : (2 - 2 * l));
      }
      h = 0;
      if (delta > 0) {
        if (max == r && max != g) h += (g - b) / delta;
        if (max == g && max != b) h += (2 + (b - r) / delta);
        if (max == b && max != r) h += (4 + (r - g) / delta);
        h /= 6;
      }
      return [h, s, l];
    }
  };

  function farbtastic(container, link, callback) {
    if (typeof link == 'function') {
      callback = link;
      link = null;
    }

    // Store farbtastic object
    var self = this
      , wheel = $('<div class="wheel" />')
      , overlay = $('<div class="overlay" />')
      , rect = $('<div class="color" />')
      , h_marker = $('<div class="h-marker marker" />')
      , sl_marker = $('<div class="sl-marker marker" />')
      , picker = $('<div class="picker" />')
        .append(rect).append(overlay).append(wheel)
        .append(h_marker).append(sl_marker)
      , container = $(container).addClass('farbtastic').append(picker)
      ;

    /**
     * Change color with HTML syntax #123456
     */
    self.setColor = function (color) {
      var unpack = self.unpack(color);
      if (! unpack) return this;
      self.color = color;
      self.rgb = unpack;
      self.hsl = self.RGBToHSL(unpack);
      self.updateDisplay();
      return this;
    }
  
    /**
     * Change color with HSL triplet [0..1, 0..1, 0..1]
     */
    self.setHSL = function (hsl) {
      self.hsl = hsl;
      self.rgb = self.HSLToRGB(hsl);
      self.color = self.pack(self.rgb);
      self.updateDisplay();
      return this;
    }
  
    /**
     * Link to the given element(s) or callback.
     */
    var updateLink = (function() { self.setColor($(this).val()); });
    self.linkTo = function (link) {
      self.color = null;
      if (self.link) $(self.link).unbind('keyup', updateLink);
      self.link = $(link).bind('keyup', updateLink).keyup();
      return this;
    }

    /////////////////////////////////////////////////////

    /**
     * Retrieve the coordinates of the given event relative to the center
     * of the widget.
     */
    self.point = function (event) {
      var pos = picker.offset();
      // Subtract distance to middle
      return { x: (event.pageX) - pos.left - picker.width()  / 2,
               y: (event.pageY) - pos.top  - picker.height() / 2};
    }
  
    function mousedown(event) {
      if (! self.dragging) {
        var pos = self.point(event)
          , circle = Math.max(Math.abs(pos.x), Math.abs(pos.y)) * 2 > self.square;
        self.dragging = circle? mousemoveCircle: mousemoveRect;
        picker.bind('mousemove', self.dragging);
        $(document).bind('mouseup', mouseup);
      }
      self.dragging(event);
      return false;
    }

    function mouseup() {
      picker.unbind('mousemove', self.dragging)
      $(document).unbind('mouseup', mouseup);
      self.dragging = false;
      if (callback) callback.call(self, self.color);
    }
  
    function mousemoveCircle(event) {
      var pos = self.point(event);
      var hue = Math.atan2(pos.x, -pos.y) / 6.28;
      if (hue < 0) hue += 1;
      self.setHSL([hue, self.hsl[1], self.hsl[2]]);
      return false;
    }
  
    function mousemoveRect(event) {
      // Get coordinates relative to color picker center
      var pos = self.point(event);
      var sat = Math.max(0, Math.min(1, -(pos.x / self.square) + .5));
      var lum = Math.max(0, Math.min(1, -(pos.y / self.square) + .5));
      self.setHSL([self.hsl[0], sat, lum]);
      return false;
    }
  
    /**
     * Update the markers and styles
     */
    self.updateDisplay = function () {
      // Markers
      var angle = self.hsl[0] * 6.28;
      h_marker.css({
        left: Math.round(Math.sin(angle) * self.radius + self.width / 2) + 'px',
        top: Math.round(-Math.cos(angle) * self.radius + self.width / 2) + 'px'
      });
  
      sl_marker.css({
        left: Math.round(self.square * (.5 - self.hsl[1]) + self.width / 2) + 'px',
        top: Math.round(self.square * (.5 - self.hsl[2]) + self.width / 2) + 'px'
      });
  
      // Saturation/Luminance gradient
      $(rect).css('backgroundColor',
        self.pack(self.HSLToRGB([self.hsl[0], 1, 0.5])));
  
      // Linked elements or callback
      $(self.link).css({
        backgroundColor: self.color, color: self.hsl[2] > 0.5 ? '#000' : '#fff' });
      $(self.link).each(function() { $(this).val(self.color); });
    }
  
  
    picker.mousedown(mousedown);
    // Fix background PNGs in IE6
    if (navigator.appVersion.match(/MSIE [0-6]\./)) {
      container.find('*').each(function () {
        if (this.currentStyle.backgroundImage == 'none') return;
        var image = this.currentStyle.backgroundImage;
        image = this.currentStyle.backgroundImage.substring(5, image.length - 2);
        $(this).css({
          'backgroundImage': 'none',
          'filter': "progid:DXImageTransform.Microsoft.AlphaImageLoader(enabled=true, sizingMethod=crop, src='" + image + "')"
        });
      });
    }
    self.setColor('#ffffff');
    self.linkTo(link);
    return self;
  }
})(jQuery);
