// The global array of objects that have been instanciated
if (!Bs_Objects) {var Bs_Objects = [];};

/**
* has to be in the global scope
*/
function bs_colPic_openWindow() {
	try {
		event.srcElement.pickerObj.openWindow();
	} catch (e) { } //well never mind. maybe the pickerObj has been removed. could happen.
}
/**
* has to be in the global scope
*/
function bs_colPic_blur() {
	try {
		event.srcElement.pickerObj.onBlur();
	} catch (e) { } //well never mind. maybe the pickerObj has been removed. could happen.
}


/**
* Color Picker class.
* 
* @param      string elementId
* @author     andrej-at-blueshoes-dot-org
* @package    javascript_components
* @subpackage colorpicker
* @copyright  blueshoes.org
*/
function Bs_ColorPicker(elementId) {
	
  // To support the old interface call with 2 arguments. (First argument used to be the object name). 
  var a = arguments;
  this._elementId = (a.length>1) ? a[1] :  a[0];  
	this._elm       = document.getElementById(this._elementId);
	
	/**
  * Unique Object/Tag ID is initialized in the constuctor.
  * Based on this._id. Can be used in genarated JS-code as ID. Is set together 
  * from the classname + this._id (see _constructor() code ).
  *
  * @access private
  * @var  string 
  */
  this._objectId;
	
	/**
	* the current value, hex code, 6 digits.
	* @access private
	* @var    string _color
	* @see    this.setColorByHex()
	*/
	this._color;
	
	/**
	* if the "background feature" should be used.
	* it makes the background of the input field the selected color.
	* and the text color of the input field is black or white, depending of 
	* the background color (dark or light).
	* @access public
	* @var    bool colorizeBackground
	*/
	this.colorizeBackground = false;
	
	/**
	* the base dir to the blueshoes javascript directory.
	* if you don't use the framework you should change it, see the examples.
	* @access public
	* @var    string jsBaseDir
	*/
	this.jsBaseDir = '/_bsJavascript/';
	
	/**
	* the blueshoes image directory.
	* if you don't use the framework you should change it, see the examples.
	* @access public
	* @var    string bsImgDir
	*/
	this.bsImgDir = '/_bsImages/';
	
	/**
	* what technology to use for the color window.
	*   dialog => window.showModalDialog(). that is the default, and works great in ie.
	*   popup  => window.open() works in mozilla, has its drawbacks.
	* 
	* note: mozilla falls back to popup anyway.
	* 
	* @access public
	* @var    string windowType
	*/
	this.windowType = 'dialog';
	
	/**
	* instance of Bs_Button. the button that opens the color picker.
	* 
	* you can access it directly and modify values. example:
	* myColorPicker.button.imgName = 'bs_fgColor';
	* 
	* see http://www.blueshoes.org/en/javascript/toolbar/
	* 
	* @access public
	* @var    object button
	*/
	this.button = new Bs_Button();
	
	
  /**
  * array holding all the information about attached events. 
	* 
  * the structure can be like these:
  * 1) attach a function directly
  *    syntax:  _attachedEvents['eventName'] = yourFunctionName;
  * 2) attach some javascript code
  *    syntax:  _attachedEvents['eventName'] = "yourCode();";
  *    example: _attachedEvents['eventName'] = "alert('hi'); callSomething('foo');";
  *    just keep in mind that you cannot use vars in that code, because when it 
  *    gets executed that will be another scope (unless the vars are global...)
  * 3) attach multiple things for the same event
  *    syntax:  _attachedEvents['eventName']    = new Array;
  *             _attachedEvents['eventName'][0] = yourFunctionName;
  *             _attachedEvents['eventName'][1] = "yourCode();";
  * 
  * @access private
  * @var    array _attachedEvents (hash, see above)
  * @see    this.attachEvent();
  */
  this._attachedEvents;	
	
	
	/**
	* the pseudo constructor.
	* @access private
	* @return void
	*/
	this._constructor = function() {
  	// Put this instance into the global object instance list
    this._id = Bs_Objects.length;
    Bs_Objects[this._id] = this; 
    this._objectId = "Bs_ColorPicker_" + this._id;
		
		this._initButton();
		this.updateColorByField();
	}
	
	/**
	* inits the button.
	* @access private
	* @return void
	*/
	this._initButton = function() {
		this.button.objectName = this._objectId + '_btnObj';
		//this.button.imgPath = '/_bsJavascript/components/numberfield/img/';
	  this.button.imgName         = 'bs_bgColor';
	  this.button.title           = 'Pick a Color';
		this.button.cssClassDefault = 'bsBtnMouseOver'; //do it anyway.
	  this.button.attachEvent('Bs_Objects['+this._id+'].openWindow();');
		eval(this._objectId + '_btnObj = this.button;');
	}
	
	/**
	* draws the open-picker button.
	* @access public
	* @return void
	*/
	this.draw = function() {
		if (!ie && !moz) return;
		
		var buttonDivId = this._objectId + '_btnDiv';
		var htmlCode = '<div id="' + buttonDivId + '" style="display:inline;"></div>';
		this._elm.insertAdjacentHTML('afterEnd', htmlCode);
		this.button.drawInto(buttonDivId);
		
		this._elm.pickerObj = this; //add a reference so we can use it later.
		this._elm.attachEvent('ondblclick', bs_colPic_openWindow);
		this._elm.attachEvent('onblur',     bs_colPic_blur);
		
		this._updateBackgroundColor();
	}
	
	/**
	* opens the colorpicker window.
	* @access public (used internally, but feel free...)
	* @return void
	*/
	this.openWindow = function() {
		var colorSelector = new Array(this.jsBaseDir + 'components/colorpicker/windowColor.html', 500, 600);
		var url = colorSelector[0] + '?objectId=' + this._objectId + '&id=' + this._id;
		url += '&callbackLoad=prepareSettings&callbackSave=setColorByPickerWindow';
		if (moz || (this.windowType == 'popup')) {
			window.open(url, "colorSelector", "width=" + colorSelector[1] + ",height=" + colorSelector[2] + ",left=0,top=0,scrollbars=no,status=no,toolbar=no,resizable=no,location=no,hotkeys=no,dependent=yes");
		} else {
			var ret = window.showModalDialog(url, this.prepareSettings(), "dialogWidth:"  + colorSelector[1] + "px; dialogHeight:" + colorSelector[2] + "px;");			
			if (ret || ((typeof(ret) == 'string') && (ret.length > 0))) this.setColorByPickerWindow(ret);
		}
	}
	
	
	/**
	* @access public (used internally by the picker window, you don't need that, use setColorByHex().)
	* @param  string hex (6 digit hex code)
	* @return void
	*/
	this.setColorByPickerWindow = function(hex) {
		if (!this.fireEvent('onBeforeChange')) return;
		this.setColorByHex(hex);
		this.fireEvent('onAfterChange');
	}
	
	/**
	* @access public
	* @param  string hex (6 digit hex code, it's ok to have a leading # (=7 digits).)
	* @return void
	*/
	this.setColorByHex = function(hex) {
		hex = this.cleanHexCode(hex);
		if ((hex == false) && ((typeof(hex) != 'string') || (hex == ''))) {
			hex = null;
			this._elm.value = '';
		} else {
			this._elm.value = '#' + hex;
		}
		this._color = hex;
		this._updateBackgroundColor();
	}
	
	/**
	* removes the trailing '#', converts to uppercase.
	* @access public
	* @param  string hex
	* @return string
	* @throws bool false (if not a valid hex code)
	*/
	this.cleanHexCode = function(hex) {
		if (bs_isEmpty(hex)) return false;
		if (hex.length == 7) hex = hex.substr(1);
		if (hex.length != 6) return false;
		return hex.toUpperCase();
	}
	
	/**
	* @access public (used internally)
	* @return void
	*/
	this.onBlur = function() {
		var oldVal = this._color;
		var newVal = this.cleanHexCode(this._elm.value);
		if (oldVal.toUpperCase() != newVal.toUpperCase()) {
			//there was a change, we have to act based on it.
			if (!this.fireEvent('onBeforeChange')) return;
			this.setColorByHex(newVal);
			this.fireEvent('onAfterChange');
		}
	}
	
	/**
	* @access private
	* @return void
	* @see    this.colorizeBackground
	*/
	this._updateBackgroundColor = function() {
		if (!this.colorizeBackground) return;
		if (bs_isEmpty(this._color)) {
			var fgColor = 'black';
			var bgColor = 'white';
		} else {
			var fgColor = isDarkColor(this._color) ? 'white' : 'black';
			var bgColor = this._color;
		}
		this._elm.style.color           = fgColor;
		this._elm.style.backgroundColor = bgColor;
	}
	
	/**
	* returns the hex color code.
	* @access public
	* @return string (6 digit hex code)
	*/
	this.getColorAsHex = function() {
		return this._color;
	}
	
	/**
	* updates the color by the input field (hex value).
	* @access public
	* @return void
	*/
	this.updateColorByField = function() {
		this.setColorByHex(this._elm.value);
	}
	
	/**
	* @access public (you don't need that, used internally and as callback for the picker window)
	* @return object (with settings)
	*/
	this.prepareSettings = function() {
		var params = new Object;
		if (!bs_isEmpty(this._color)) {
			params.color = this._color;
		}
		params.bsImgDir = this.bsImgDir;
		return params;
	}
	
	
  /**
  * attaches an event.
	* 
	* the following triggers can be used:
	*   'onBeforeChange'
	*   'onAfterChange'
	* 
	* the onXXXChange events fire when the value changes by the user, not 
	* when you do it using an api method like setColorByHex() because 
	* then you already know what's happening.
	* 
	* the events will be executed in the order they were registered.
	* 
	* if an onBeforeXXX event you've attached returns bool FALSE, it 
	* will stop executing any other attached events in that queue, 
	* and it will quit. example: if you attach an onBeforeChange 
	* event, and your code returns FALSE, the change won't be done 
	* at all.
	* 
	* examples:
	*   myObj.attachEvent('onBeforeChange', myFunction);
	*   then your function myFunction() receives one param, it is 
	*   a reference to this object (myObj).
	*   
	*   myObj.attachEvent('onBeforeChange', "if (true) return false;");
	*   this is an example with code attached that will be evaluated.
	* 
  * @access public
  * @param  string trigger
  * @param  mixed  yourEvent (string (of code) or function)
  * @return void
  * @see    var this._attachedEvents
  */
  this.attachEvent = function(trigger, yourEvent) {
    if (typeof(this._attachedEvents) == 'undefined') {
      this._attachedEvents = new Array();
    }
    
    if (typeof(this._attachedEvents[trigger]) == 'undefined') {
      this._attachedEvents[trigger] = new Array(yourEvent);
    } else {
      this._attachedEvents[trigger][this._attachedEvents[trigger].length] = yourEvent;
    }
  }
	
  /**
  * tells if any event is attached for the trigger specified. 
  * @access public
  * @param  string trigger
  * @return bool
  */
  this.hasEventAttached = function(trigger) {
    return (this._attachedEvents && this._attachedEvents[trigger]);
  }
  
  /**
  * fires the events for the trigger specified.
  * @access public (used internally but feel free to trigger events yourself...)
  * @param  string trigger
  * @return void
  */
  this.fireEvent = function(trigger) {
    if (this._attachedEvents && this._attachedEvents[trigger]) {
      var e = this._attachedEvents[trigger];
      if ((typeof(e) == 'string') || (typeof(e) == 'function')) {
        e = new Array(e);
      }
      for (var i=0; i<e.length; i++) {
        if (typeof(e[i]) == 'function') {
          var status = e[i](this);
        } else if (typeof(e[i]) == 'string') {
          var status = eval(e[i]);
        } //else murphy
				if (status == false) return false;
      }
    }
		return true;
  }	

		
	this._constructor(); //call the constructor. needs to be at the end.	

}
