// Copyright (c) 2006 Michael Daines (http://www.mdaines.com)
// 
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
// 
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.


// Constrain a number between two values.
Number.prototype.constrain = function(lower, upper) {
  if ( this > upper) return upper;
  else if (this < lower) return lower;
  else return this;
}

// Constrain a draggable element within its parent (give this to Draggable for snapping).
Element.constrain_within_parent = function(x,y,draggable) {
  element_dimensions = Element.getDimensions(draggable.element);
  parent_dimensions = Element.getDimensions(draggable.element.parentNode);
  
  x = new Number(x).constrain(0 - element_dimensions.width/2, parent_dimensions.width - element_dimensions.width/2);
  y = new Number(y).constrain(0 - element_dimensions.height/2, parent_dimensions.height - element_dimensions.height/2);
  
  return [x,y];
}

// We have to open up this function in script.aculo.us to get the snapping code to pass
// the Draggable. This is horrible, of course. It'd be nice to have this in script.aculo.us!
Draggable.prototype.draw = function(point) {
  var pos = Position.cumulativeOffset(this.element);
  var d = this.currentDelta();
  pos[0] -= d[0]; pos[1] -= d[1];
  
  if(this.options.scroll) {
    pos[0] -= this.options.scroll.scrollLeft-this.originalScrollLeft;
    pos[1] -= this.options.scroll.scrollTop-this.originalScrollTop;
  }
  
  var p = [0,1].map(function(i){ 
    return (point[i]-pos[i]-this.offset[i]) 
  }.bind(this));
  
  if(this.options.snap) {
    if(typeof this.options.snap == 'function') {
      p = this.options.snap(p[0],p[1],this);
    } else {
    if(this.options.snap instanceof Array) {
      p = p.map( function(v, i) {
        return Math.round(v/this.options.snap[i])*this.options.snap[i] }.bind(this))
    } else {
      p = p.map( function(v) {
        return Math.round(v/this.options.snap)*this.options.snap }.bind(this))
    }
  }}
  
  var style = this.element.style;
  if((!this.options.constraint) || (this.options.constraint=='horizontal'))
    style.left = p[0] + "px";
  if((!this.options.constraint) || (this.options.constraint=='vertical'))
    style.top  = p[1] + "px";
  if(style.visibility=="hidden") style.visibility = ""; // fix gecko rendering
}


var ColorSelector = {
  Version: '0.5.1',
  
  selectors: new Array(),
  options: function(element){
    element = $(element);
    return this.selectors.detect(function(s) { return s.element == element });
  },
  destroy: function(element){
    element = $(element);
    this.selectors = this.selectors.reject(function(s) { return s.element == element });
  },
  
  // Create a color selector.
  //
  // Required options are:
  //
  // * transforms, an object with functions for transforming positions to colors, etc.
  //   see included transforms.js for an example.
  // * value or field, either an initial value, specified in the way your transforms
  //   expect it (the included example expects a text string with three numbers), or
  //   a field to get a value from.
  //
  // You can also specify the ID of a dropper element in case you want to use one besides
  // the first non-text node inside the element you call this on.
  //
  create: function(element) {
    var options = arguments[1] || {};

    options.element = $(element);
    options.dropper = $(options.dropper);
    options.field = $(options.field);
    
    // if no dropper is specified, look for the first non-text node inside the element
    if (!options.dropper) options.dropper = $A(options.element.childNodes).detect(function(n){return n.nodeType != 3});
    
    this.destroy(options.element);
    
    new Draggable(options.dropper, {
      snap: Element.constrain_within_parent,
      change: function(){ColorSelector.reflect_selected_color(options.element)},
      starteffect: Prototype.emptyFunction,
      endeffect: Prototype.emptyFunction});
    
    this.selectors.push(options);
    
    // if a value field was specified, select the value it contains, otherwise select the supplied value
    if (options.field)
      this.select_color(options.element, options.transforms.value_to_color(options.field.value));
    else
      this.select_color(options.element, options.transforms.value_to_color(options.value));
  },
  
  select_color: function(element, color) {
    var options = this.options(element);
    
    var offset = options.transforms.color_to_offset(color);
    var dropper_dimensions = Element.getDimensions(options.dropper);
    
    options.dropper.style.left = offset[0] - ((dropper_dimensions.width+1)/2) + 'px';
    options.dropper.style.top = offset[1] - ((dropper_dimensions.height+1)/2) + 'px';
  
    this.reflect_selected_color(element);
  },
  
  reflect_selected_color: function(element) {
    var options = this.options(element);
    
    var pos = this.position(element);
    var color = options.transforms.offset_to_color(pos[0], pos[1]);
    
    options.dropper.style.background = options.transforms.color_to_style(color);
    if (options.field) options.field.value = options.transforms.color_to_value(color);
  },
  
  position: function(element) {
    var options = this.options(element);
    
    var dropper_offset = Position.cumulativeOffset(options.dropper);
    var dropper_dimensions = Element.getDimensions(options.dropper);
    var colors_offset = Position.cumulativeOffset(options.element);
    
    return [  dropper_offset[0] + ((dropper_dimensions.width+1)/2) - colors_offset[0],
              dropper_offset[1] + ((dropper_dimensions.height+1)/2) - colors_offset[1] ];
  },
  
  color: function(element) {
    var options = this.options(element);
    var position = this.position(element);
    return options.transforms.offset_to_color(position[0], position[1]);
  },
  
  value: function(element) {
    var options = this.options(element);
    return options.transforms.color_to_value(this.color(element));
  }
}