// MISC UTILITIES

// find the minimum value of an array
Array.prototype.min = function() {
	var result = this[0];
	$.map(this.slice(1), function(val) { if (result > val) result = val; });
	return result;
}

// clamp a value to within a certain range
function clamp(value, bottom, top) {
	if (value < bottom)
		return bottom;
	else if (value > top)
		return top;
	else
		return value;
}

// This is our Color type which encapsulates all of the crap we need to do with colors.
function Color(red, green, blue, hexValue) {
	this.red   = clamp(red, 0, 255);
	this.green = clamp(green, 0, 255);
	this.blue  = clamp(blue, 0, 255);
	this.hex   = hexValue;
	
	// different representations
	this.toHex = function() {
		if (this.hex)
			return this.hex;
		
		this.hex = sprintf("%02x%02x%02x", this.red, this.green, this.blue);
		return this.hex;
	}
	
	// shades
	this.makeDarker = function() { 
		return new Color(this.red - 20, this.green - 20, this.blue - 20);
	}
	
	this.makeLighter = function() { 
		return new Color(this.red + 20, this.green + 20, this.blue + 20);
	}
	
	// color distances
	this.distanceTo = function(other) { 
		return Math.sqrt(Math.pow( 	other.red  - this.red, 2) +
		                 Math.pow( other.green - this.green, 2) +
		                 Math.pow(  other.blue - this.blue, 2));
	}
	
	// halfway point
	this.findHalf = function (other) {
		var process = function(color1, color2) { 
			return Math.floor((Math.abs(color1 - color2) / 2) + [color1, color2].min());
		}
		
		return new Color(process(this.red, other.red), 
		                 process(this.green, other.green), 
		                 process(this.blue, other.blue));
	}
	
	// PMS color
	this.toPMS = function() {
		// see if we get lucky
		if ((pt = PANTONE.get(this.toHex())) !== undefined)
			return pt;
		
		// do annoying search instead
		var min_distance = Infinity; 
		var winner = null;
		var my = this;
		
		$.each(PANTONE.items(), function(i, item) {
			var key = item[0],
			    value = item[1];
			//console.log("Examining " + key + " and " + value);
			
			if ((thisDistance = my.distanceTo(Color.fromHex(key))) < min_distance) {
				min_distance = thisDistance;
				winner = value;
			}
		});
		
		return winner;
	}
	
	// Locate the opposite of this color
	this.opposite = function() {
		var red   = this.green/2 + this.blue/2,
		    green = this.red/2 + this.blue/2,
		    blue  = this.red/2 + this.green/2;
		
		return new Color(Math.floor(red), Math.floor(green), Math.floor(blue));
	}
	
	this.toString = this.toHex;
}

_colorHexes = new Hash();

// An alternate constructor for cases where you have the hex string instead of the color parts
Color.fromHex = function(colorString) {
	// return a memoized version if possible
	if (res = _colorHexes.get(colorString))
		return res;
	
	// none was found, so create a new one
	c = new Color(parseInt(colorString.substring(0,2), 16),
	              parseInt(colorString.substring(2,4), 16),
	              parseInt(colorString.substring(4,6), 16),
	              colorString);
	
	// memoize it
	_colorHexes.put(colorString, c);
	
	return c;
}

Color.fromPMS = function(pms) {
	return Color.fromHex(PANTONE_INV.get(pms));
}
