/* <copyright>
This file contains proprietary software owned by Motorola Mobility, Inc.<br/>
No rights, expressed or implied, whatsoever to this software are provided by Motorola Mobility, Inc. hereunder.<br/>
(c) Copyright 2011 Motorola Mobility, Inc.  All Rights Reserved.
</copyright> */

////////////////////////////////////////////////////////////////////////
//
var Montage = 			require("montage/core/core").Montage,
	Component = 		require("montage/ui/component").Component;
////////////////////////////////////////////////////////////////////////
//
exports.ColorModel = Montage.create(Component, {
	////////////////////////////////////////////////////////////////////
    //
	hasTemplate: {
		enumerable: false,
    	value: false
	},
	////////////////////////////////////////////////////////////////////
    //HSV Value of current color selected
    _gradient: {
    	enumerable: false,
    	value: null
    },
    ////////////////////////////////////////////////////////////////////
    //HSV Value of current color selected
    gradient: {
    	enumerable: true,
        get: function() {
            return this._gradient;
        },
        set: function(value) {
        	this._gradient = value;
        	//Updating color selected (converting to all modes)
        	this.updateColorSelected('gradient', value);
        }
    },
    ////////////////////////////////////////////////////////////////////
    //HSV Value of current color selected
    _hsv: {
    	enumerable: false,
    	value: null
    },
    ////////////////////////////////////////////////////////////////////
    //HSV Value of current color selected
    hsv: {
    	enumerable: true,
        get: function() {
            return this._hsv;
        },
        set: function(value) {
        	this._hsv = value;
        	//Updating color selected (converting to all modes)
        	this.updateColorSelected('hsv', value);
        }
    },
    ////////////////////////////////////////////////////////////////////
    //RGB Value of current color selected
    _rgb: {
    	enumerable: false,
    	value: null
    },
    ////////////////////////////////////////////////////////////////////
    //RGB Value of current color selected
    rgb: {
    	enumerable: true,
        get: function() {
            return this._rgb;
        },
        set: function(value) {
        	this._rgb = value;
        	//Updating color selected (converting to all modes)
        	this.updateColorSelected('rgb', value);
        }
    },
    ////////////////////////////////////////////////////////////////////
    //HSL Value of current color selected
    _hsl: {
    	enumerable: false,
    	value: null
    },
    ////////////////////////////////////////////////////////////////////
    //HSL Value of current color selected
    hsl: {
    	enumerable: true,
        get: function() {
            return this._hsl;
        },
        set: function(value) {
        	this._hsl = value;
        	//Updating color selected (converting to all modes)
        	this.updateColorSelected('hsl', value);
        }
    },
    ////////////////////////////////////////////////////////////////////
    //HEX Value of current color selected
    _hex: {
    	numerable: false,
    	value: null
    },
    ////////////////////////////////////////////////////////////////////
    //HEX Value of current color selected
    hex: {
    	enumerable: true,
        get: function() {
            return this._hex;
        },
        set: function(value) {
        	this._hex = value;
            //Updating color selected (converting to all modes)
        	this.updateColorSelected('hex', value);
        }
    },
    ////////////////////////////////////////////////////////////////////
    //ALPHA Value of current color selected
    _alpha: {
    	enumerable: false,
    	value: {value: 1, type: 'change', wasSetByCode: true}
    },
    ////////////////////////////////////////////////////////////////////
    //ALPHA Value of current color selected
    alpha: {
    	enumerable: true,
        get: function() {
            return this._alpha;
        },
        set: function(value) {
        	value.value = Math.ceil(value.value*100)/100;
        	this._alpha = value;
        	//
        	if (this.rgb || this.hsl) {
        		this._dispatchChangeEvent('alpha', value);     	
        	}
        }
    },
    ////////////////////////////////////////////////////////////////////
    //Input (fill or stroke) Value of current color selected
    _input: {
    	enumerable: false,
    	value: 'fill'
    },
    ////////////////////////////////////////////////////////////////////
    //Input Value of current color selected
    input: {
    	enumerable: true,
        get: function() {
            return this._input;
        },
        set: function(value) {
        	this._input = value;
        	//Dispatching change event
            this._dispatchChangeEvent('input', value);
        }
    },
    ////////////////////////////////////////////////////////////////////
    //Color mode of current color selected
    _mode: {
    	enumerable: false,
    	value: null
    },
    ////////////////////////////////////////////////////////////////////
    //Color mode of current color selected
    mode: {
    	enumerable: true,
        get: function() {
            return this._mode;
        },
        set: function(value) {
        	this._mode = value;
        	//Dispatching change event
            this._dispatchChangeEvent('mode', value);
        }
    },
    ////////////////////////////////////////////////////////////////////
    //Stroke Color Value of current color selected
    _stroke: {
    	enumerable: false,
    	value: null
    },
    ////////////////////////////////////////////////////////////////////
    //Stroke Color Value of current color selected
    stroke: {
    	enumerable: true,
        get: function() {
            return this._stroke;
        },
        set: function(value) {
        	this._stroke = value;
        }
    },
    ////////////////////////////////////////////////////////////////////
    //Fill Color Value of current color selected
    _fill: {
    	enumerable: false,
    	value: null
    },
    ////////////////////////////////////////////////////////////////////
    //Fill Color Value of current color selected
    fill: {
    	enumerable: true,
        get: function() {
            return this._fill;
        },
        set: function(value) {
        	this._fill = value;
        }
    },
    ////////////////////////////////////////////////////////////////////
    //History Value array of current color selected
    colorHistory: {
    	enumerable: false,
    	value: {stroke: [{m: 'rgb', c: {r: 0, g: 0, b: 0}, a: 1}, {m: 'rgb', c: {r: 0, g: 0, b: 0}, a: 1}], fill: [{m: 'rgb', c: {r: 0, g: 0, b: 0}, a: 1}, {m: 'rgb', c: {r: 0, g: 0, b: 0}, a: 1}]}
    },
    ////////////////////////////////////////////////////////////////////
    //History Value array of current color selected
    _addColorHistory: {
    	enumerable: true,
        value: function(input, mode, color, alpha) {
        	//TODO: Add limit
        	if (this.colorHistory[input.toLowerCase()].length > 1) {
        		if (this.colorHistory[input.toLowerCase()][this.colorHistory[input.toLowerCase()].length-1].c !== color || this.colorHistory[input.toLowerCase()][this.colorHistory[input.toLowerCase()].length-1].a !== alpha.value) {
        			this.colorHistory[input.toLowerCase()].push({m: mode, c: color, a: alpha.value});
        		}
        	} else {
        		this.colorHistory[input.toLowerCase()].push({m: mode, c: color, a: alpha.value});
        	}
        }
    },
    ////////////////////////////////////////////////////////////////////
    //
    applyNoColor: {
    	enumerable: true,
        value: function () {
        	//
        	var nocolor = {};
        	nocolor.wasSetByCode = true;
    		nocolor.type = 'change';
        	this.updateColorSelected('nocolor', nocolor);
        }
    },
    ////////////////////////////////////////////////////////////////////
    //Method to update color selected and convert to other modes
    updateColorSelected: {
    	enumerable: true,
    	//value: function (input, mode, color, alpha) {
    	value: function (mode, color) {
    		////////////////////////////////////////////////////////////
    		//Checking for color mode to convert colors
    		switch (mode.toLocaleLowerCase()) {
    			////////////////////////////////////////////////////////
    			case 'gradient':
    				//Checking for match of previous gradient
    				if (color !== this.gradient) {
    					//Setting value and breaking out of function
    					this.gradient = color;
    					return;
    				}
    				//
    				this._hex = '------';
    				this._rgb = null;
    				this._hsv = null;
    				this._hsl = null;
    				break;
    			////////////////////////////////////////////////////////
    			case 'rgb':
    				//Checking for match of previous (RGB)
    				if (color.r !== this.rgb.r && color.g !== this.rgb.g && color.b !== this.rgb.b) {
    					//Setting value and breaking out of function
    					this.rgb = color;
    					return;
    				}
    				//Setting other color mode values
    				this._hsv = this.rgbToHsv(color.r, color.g, color.b);
    				this._hsl = this.rgbToHsl(color.r, color.g, color.b);
    				this._hex = this.rgbToHex(color.r, color.g, color.b);
    				this._gradient = null;
    				break;
    			////////////////////////////////////////////////////////
    			case 'hsv':
    				//Checking for match of previous (HSV)
    				if (color.h !== this.hsv.h && color.s !== this.hsv.s && color.v !== this.hsv.v) {
    					//Setting value and breaking out of function
    					this.hsv = color;
    					return;
    				}
    				//Setting other color mode values
    				this._rgb = this.hsvToRgb(color.h/(2*Math.PI), color.s, color.v);
    				this._hsl = this.rgbToHsl(this.rgb.r, this.rgb.g, this.rgb.b);
    				this._hex = this.rgbToHex(this.rgb.r, this.rgb.g, this.rgb.b);
    				this._gradient = null;
    				break;
    			////////////////////////////////////////////////////////
    			case 'hsl':
    				//Checking for match of previous (HSV)
    				if (color.h !== this.hsl.h && color.s !== this.hsl.s && color.l !== this.hsl.l) {
    					//Setting value and breaking out of function
    					this.hsl = color;
    					return;
    				}
    				//Setting other color mode values
    				this._rgb = this.hslToRgb(color.h/360, color.s/100, color.l/100);
    				//This is a hack to keep the values of color spectrum the same on limits (B/W)
    				var hsvTemp = this.rgbToHsv(this.rgb.r, this.rgb.g, this.rgb.b);
    				this._hsv = {h: (this._hsl.h/360)*(2*Math.PI), s: hsvTemp.s, v: hsvTemp.v};
    				this._hex = this.rgbToHex(this.rgb.r, this.rgb.g, this.rgb.b);
    				this._gradient = null;
    				break;
    			////////////////////////////////////////////////////////
    			case 'hex':
    				switch (color.length) {
    					case 1:
    						this.applyNoColor();
    						return;
    						break;
    					case 2:
    						this.applyNoColor();
    						return;
    						break;
    					case 3:
    						color = color[0]+color[0]+color[1]+color[1]+color[2]+color[2];
    						break;
    					case 4:
    						this.applyNoColor();
    						return;
    						break;
    					case 5:
    						this.applyNoColor();
    						return;
    						break;
    					case 6:
    						//Nothing
    						break;
    					default:
    						this.applyNoColor();
    						return;
    						break;
    				}
    				//Checking for match of previous (HEX)
    				if (color !== this.hex) {
    					//Setting value and breaking out of function
    					this.hex = color;
    					return;
    				}
    				//Setting other color mode values
    				this._rgb = this.hexToRgb(color);
    				this._hsv = this.rgbToHsv(this.rgb.r, this.rgb.g, this.rgb.b);
    				this._hsl = this.rgbToHsl(this.rgb.r, this.rgb.g, this.rgb.b);
    				this._gradient = null;
    				break;
    			////////////////////////////////////////////////////////
    			case 'nocolor':
    				//
    				this._hex = '------';
    				this._rgb = null;
    				this._hsv = null;
    				this._hsl = null;
    				this._gradient = null;
    				break;
    			////////////////////////////////////////////////////////
    			default:
    				console.log('ERROR: An error occurred in ColorManager. '+mode+' was not identified in updateColorSelected');
    				break;
    			////////////////////////////////////////////////////////
    		}
    		//Dispatching change event
    		this._dispatchChangeEvent(mode, color);
    	}
    },
    ////////////////////////////////////////////////////////////////////
    //Dispatching "Change" event
    _dispatchChangeEvent: {
    	//Modes: rgb, hsl, hsv, hex, alpha, input, mode
        value: function(mode, value) {
        	//Creating custom event to dispatch and include values
            var colorEvent = document.createEvent("CustomEvent");
            //
            if (value && value.type) {
            	if (value.type === "change") {
            		//
            		colorEvent.initEvent("change", true, true);
    				//Storing the selected color in the history array
    				if ((mode === 'rgb' || mode === 'hsv' || mode === 'hsl' || mode === 'hex' || mode === 'nocolor' || mode === 'gradient') && this.input !== 'chip') {
		    			this._addColorHistory(this.input, mode, value, this.alpha);
		    		} else if (mode === 'alpha' && this.input !== 'chip') {
		    			var c = this.colorHistory[this.input][this.colorHistory[this.input].length-1];
		    			this._addColorHistory(this.input, c.m, c.c, this.alpha);
		    		}
            	} else if (value.type === "changing") {
            		//
            		colorEvent.initEvent("changing", true, true);
            	} else {
            		//
            		colorEvent.initEvent("error", true, true);
            	}
            } else {
            	//
            	colorEvent.initEvent("error", true, true);
            }
            //Checking for event to be color, if so, adding color data
            if (mode === 'rgb' || mode === 'hsv' || mode === 'hsl' || mode === 'hex' || mode === 'alpha' || mode === 'nocolor' || mode === 'gradient') {
            	//Returning color history of input
            	if (this.input === 'stroke') {
            		colorEvent.history = this.colorHistory.stroke;
            	} else if (this.input === 'fill') {
            		colorEvent.history = this.colorHistory.fill;
            	} else {
            		colorEvent.history = null;
            	}
            }
            //Also returning other color mode values
			if (this.input)
	            colorEvent.input = this.input;
			if (this.alpha)
	            colorEvent.alpha = this.alpha.value;
           	if (this.hsl)
           		colorEvent.hsla = {h: this.hsl.h, s: this.hsl.s, l: this.hsl.l, a: this.alpha.value, css: 'hsla('+this.hsl.h+', '+this.hsl.s+'%, '+this.hsl.l+'%, '+this.alpha.value+')'};
            if (this.rgb)
	            colorEvent.rgba = {r: this.rgb.r, g: this.rgb.g, b: this.rgb.b, a: this.alpha.value, css: 'rgba('+ this.rgb.r +', '+this.rgb.g+', '+this.rgb.b+', '+this.alpha.value+')'};
           	if (this.hex)
	           	colorEvent.hex = this.hex;
			//Standard values that apply to any event	           
            colorEvent.value = value;
            colorEvent.mode = mode;
            if (value && value.wasSetByCode) {
            	colorEvent.wasSetByCode = value.wasSetByCode;
            }
            this.dispatchEvent(colorEvent);
        }
    },
    ////////////////////////////////////////////////////////////////////
    ////////////////////////////////////////////////////////////////////
    // COLOR CONVERTION METHODS (public)
    ////////////////////////////////////////////////////////////////////
    ////////////////////////////////////////////////////////////////////
    //Convert RGB to HEX (RGB = 0 to 255)
	rgbToHex: {
		enumerable: true,
		value: function (r, g, b) {
			//Coverting color channel to Hex
			function getHex (c) {
				var filter = "0123456789ABCDEF";
				c = parseInt (c, 10);
				if (isNaN(c)) return "00";
 				c = Math.max(0,Math.min(c,255));
 				return filter.charAt((c-c%16)/16) + filter.charAt(c%16);
			}
			//Returning HEX string
			return (getHex(r)+getHex(g)+getHex(b));
		}
	},
	////////////////////////////////////////////////////////////////////
    //Convert HEX to RGB (no # symbol)
	hexToRgb: {
		enumerable: true,
		value: function (value) {
			//Spliting string converting to values
			var r = parseInt(value.substring(0,2), 16), g = parseInt(value.substring(2,4), 16), b = parseInt(value.substring(4,6), 16);
			//Checking for valid values
			if (isNaN(r)) return null;
			if (isNaN(g)) return null;
			if (isNaN(b)) return null;
			return {r: r, g: g, b: b};
		}
	},
	////////////////////////////////////////////////////////////////////
    //Convert HSV to RGB (HSV = 0 to 1)
    hsvToRgb: {
    	enumerable: true,
    	value: function (h, s, v) {
    		var r, g, b, i = Math.floor(h * 6), f = h * 6 - i, p = v * (1 - s), q = v * (1 - f * s), t = v * (1 - (1 - f) * s);
    		//
    		switch (i % 6){
	       		case 0: r = v, g = t, b = p; break;
		    	case 1: r = q, g = v, b = p; break;
        		case 2: r = p, g = v, b = t; break;
			    case 3: r = p, g = q, b = v; break;
	        	case 4: r = t, g = p, b = v; break;
	        	case 5: r = v, g = p, b = q; break;
   			}
   			//Returning RGB object
   			return {r: Math.round(r * 255), g: Math.round(g * 255), b: Math.round(b * 255)};
    	}
    },
    ////////////////////////////////////////////////////////////////////
	//Convert RGB to HSV (RGB = 0 to 255)
	rgbToHsv: {
		enumerable: true,
    	value: function (r, g, b) {
			//RGB covertion to percentage
    		r = r/255, g = g/255, b = b/255;
    		var max = Math.max(r, g, b), min = Math.min(r, g, b), h, s, v, d;
    		h = s = v = max;
    		d = max - min;
  	  		s = max == 0 ? 0 : d / max;
			//TODO: Check for Hue not to change if S or V = 0
			if (max == min) {
        		h = 0;
    		} else {
        		switch (max) {
            		case r: h = (g - b) / d + (g < b ? 6 : 0); break;
            		case g: h = (b - r) / d + 2; break;
            		case b: h = (r - g) / d + 4; break;
        		}
        		h /= 6;
    		}
			//Returing HSV object
    		return {h: h*Math.PI*2, s: s, v: v};
		}
	},
    ////////////////////////////////////////////////////////////////////
	//Convert RGB TO HSL (RGB = 0 to 255)
	rgbToHsl: {
		enumerable: true,
    	value: function (r, g, b) {
    		//RGB covertion to percentage
    		r /= 255, g /= 255, b /= 255;
    		var max = Math.max(r, g, b), min = Math.min(r, g, b), h, s, l, d;
			h = s = l = (max + min) / 2;
			//TODO: Check for Hue not to change if S or L = 0
			if (max == min) {
        		h = s = 0;
    		} else {
        		d = max - min;
        		s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
        		switch (max) {
            		case r: h = (g - b) / d + (g < b ? 6 : 0); break;
 	           		case g: h = (b - r) / d + 2; break;
            		case b: h = (r - g) / d + 4; break;
        		}
        		h /= 6;
    		}
    		//Returing HSL object
    		return {h: h*360, s: s*100, l: l*100};
    	}
	},
	////////////////////////////////////////////////////////////////////
	//Convert HSL to RGB (HSL = 0 to 1)
	hslToRgb: {
		enumerable: true,
    	value: function (h, s, l) {
		 	var r, g, b;
		 	if (s == 0) {
        		r = g = b = l;
    		} else {
        		function getRgb (p, q, t) {
            		if(t < 0) t += 1;
            		if(t > 1) t -= 1;
            		if(t < 1/6) return p + (q - p) * 6 * t;
            		if(t < 1/2) return q;
            		if(t < 2/3) return p + (q - p) * (2/3 - t) * 6;
            		return p;
        		}
				var q = l < 0.5 ? l * (1 + s) : l + s - l * s;
        		var p = 2 * l - q;
        		r = getRgb(p, q, h + 1/3);
        		g = getRgb(p, q, h);
        		b = getRgb(p, q, h - 1/3);
    		}
			//Returning RGB object
			return {r: Math.round(r * 255), g: Math.round(g * 255), b: Math.round(b * 255)};
    	}
	}
	////////////////////////////////////////////////////////////////////
    ////////////////////////////////////////////////////////////////////
	////////////////////////////////////////////////////////////////////
});