/* Copyright (c) 2011, Geert Bergman (geert@scrivo.nl)
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 * 3. Neither the name of "Scrivo" nor the names of its contributors may be
 *    used to endorse or promote products derived from this software without
 *    specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 * $Id: color.js 750 2013-07-23 01:48:11Z geert $
 */

"use strict";

/**
 * @summary
 * The SUI.color namespace contains a set of utility functions for working
 * with color codes. 
 * 
 * @description
 * Colors can be defined in different was. Popular methods use HSV
 * or RGB color values. The latter can be represented in different ways in
 * CSS: '#0010FF', '#01F' or 'rgb(0,16,255)'. In this namespace you'll find a
 * set of functions for conversions between HTML/CSS color codes, RGB and HSV 
 * color values.
 * 
 * @namespace
 */
SUI.color = {

	/**
	 * Convert an HTML or CSS color code to a standard HTML color code
	 * (standardize color code).
	 * @param {String} c A HTML or CSS color code, the following formats
	 *    are allowed: #FFF, #FFFFFF, rgb(255,255,255).
	 * @return {String} An HTML color code (#FFFFFF).
	 */
	colToCol: function(c) {
		return SUI.color.rgbToCol(SUI.color.colToRgb(c));
	},

	/**
	 * Convert an HTML or CSS color code to an HSV color definition.
	 * @param {String} c A HTML or CSS color code to convert to an HSV color
	 *    object, the following formats are allowed: #FFF, #FFFFFF and
	 *    rgb(255,255,255).
	 * @return {Object} An HSV color object with the following members:
	 *    h (hue: 0-360), s: (saturation: 0-1), v: (value: 0-1)
	 */
	colToHsv: function(c) {
		return SUI.color.rgbToHsv(SUI.color.colToRgb(c));
	},

	/**
	 * Convert an HTML or CSS color code to RGB color fractions.
	 * @param {String} c A HTML or CSS color code to convert to a RGB color
	 *    object, the following formats are allowed: #FFF, #FFFFFF and
	 *    rgb(255,255,255).
	 * @return {Object} An RGB color object with the following members:
	 *    r (red: 0-1), g (green: 0-1) and b (blue: 0-1).
	 */
	colToRgb: function(c) {
		// assume base 16 (hexadecimal 0x00-0xFF)
		var b = 16;
		// see if c matches #F70 ...
		var x = /^#(\w{1})(\w{1})(\w{1})$/.exec(c);
		if (x) {
			// ... yes: change F to FF ...
			for (var i=1; i<=3; i++) {
				x[i] = x[i]+""+x[i];
			}
		} else {
			// ... no, see if c matches #FF7700 ...
			var x = /^#(\w{2})(\w{2})(\w{2})$/.exec(c);
			if (!x) {
				// ... no, go for base 10 ...
				b = 10;
				// ... and see is c matches rrb(255, 127, 0) ...
				x = /^rgb\((\d{1,3}),\s*(\d{1,3}),\s*(\d{1,3})\)$/.exec(c);
				if (!x) {
					// ... no: hopeless, return black
					return {r: 0, g: 0, b: 0};
				}
			}
		}
		// parse and return the RGB values using the correct base
		return {r: parseInt(x[1],b)/255, g: parseInt(x[2],b)/255,
			b: parseInt(x[3],b)/255};
	},

	/**
	 * Convert a HSV color definition to an HTML color code.
	 * @param {Object} c An HSV color object with the following members:
	 *    h (hue: 0-360), s: (saturation: 0-1), v: (value: 0-1) to convert to
	 *    an HTML color code.
	 * @return {String} An HTML color code (#FFFFFF)
	 */
	hsvToCol: function(c) {
		return SUI.color.rgbToCol(SUI.color.hsvToRgb(c));
	},

	/**
	 * Convert a HSV color definition RGB color fractions.
	 * @param {Object} c An HSV color object with the following members:
	 *    h (hue: 0-360), s: (saturation: 0-1), v: (value: 0-1) to convert to
	 *    an HSV color object.
	 * @return {Object} An object with the following members: r, g and b,
	 *    each within a range of 0 to 1.
	 */
	hsvToRgb: function(c) {
		// if the saturation is zero ...
		if (c.s == 0) {
			// ... return gray based on the color's value
			return {r: c.v, g: c.v, b: c.v};
		}
		// find the sector (0-5)
		c.h /= 60;
		var sect = Math.floor(c.h);
		// get the fraction of the hue in the sector
		var f = c.h - sect;

		// c.v     _ q     t _     t _ _ q             t _ _ q
		// p         \ _ _ /       /     \ _ _     _ _ /     \
		// sect  R 0 1 2 3 4 5   G 0 1 2 3 4 5   B 0 1 2 3 4 5

		// value for the sector between high and low color sections
		var q = c.v * (1 - c.s * f);
		// values for the 2 sectors with the low color values
		var p = c.v * (1 - c.s);
		// value for the sector between low and high color sections
		var t = c.v * (1 - c.s * (1 - f) );

		// map the results to the right RGB components and return
		switch (sect) {
			case 1:
				return {r: q, g: c.v, b: p};
			case 2:
				return {r: p, g: c.v, b: t};
			case 3:
				return {r: p, g: q, b: c.v};
			case 4:
				return {r: t, g: p, b: c.v};
			case 5:
				return {r: c.v, g: p, b: q};
			default:
				return {r: c.v, g: t, b: p};
		}
	},

	/**
	 * Convert a RGB color fractions to an HTML color code.
	 * @param {Object} c An RGB color object with the following members:
	 *    r (red: 0-1), g (green: 0-1) and b (blue: 0-1) to convert to an
	 *    HTML color code.
	 * @return {String} An HTML color code (#FFFFFF).
	 */
	rgbToCol: function(c) {
		// build a hexadecimal color value and 0x1000000 to it ...
		var x = 0x1000000 + (c.r*255|0)*65536 + (c.g*255|0)*256 + (c.b*255|0);
		// ... convert it to string, chop of the leading 1 and return it
		return "#" + x.toString(16).substr(1);
	},

	/**
	 * Convert a RGB color fractions to an HTML color code.
	 * @param {Object} c An RGB color object with the following members:
	 *    r (red: 0-1), g (green: 0-1) and b (blue: 0-1) to convert to an
	 *    HSV color object.
	 * @return {Object} An HSV color object with the following members:
	 *    h (hue: 0-360), s: (saturation: 0-1), v: (value: 0-1)
	 */
	rgbToHsv: function(c) {
		// get the minimum value of the RGB components
		var min = (c.r<c.g ? c.r : c.g)<c.b ? (c.r<c.g ? c.r : c.g) : c.b;
		// get the maximum value of the RGB components
		var max = (c.r>c.g ? c.r : c.g)>c.b ? (c.r>c.g ? c.r : c.g) : c.b;
		// start with black
		var r = {h: 0, s: 0, v: 0};

		var chroma = max - min;

		// the value is the max component
		r.v = max;
		if (!chroma || r.v == 0) {
			// if there is no chroma (and guard against division by zero)
			r.h = 360;
			return r;
		} else {
			// the saturation is the chroma as fraction of the value
			r.s = chroma/r.v;
		}

		// get the hue as a sector + fraction
		if(c.r == max) {
			r.h = (c.g - c.b) / chroma;
		} else if(c.g == max) {
			r.h = 2 + (c.b - c.r) / chroma;
		} else {
			r.h = 4 + (c.r - c.g) / chroma;
		}

		// convert the hue from sector to degrees
		r.h *= 60;
		if(r.h < 0) {
			r.h += 360;
		}

		// return the result
		return r;
	}

};