/** uupaa-color.js
 *
 * uupaa-color.js is Color and Color Scheme support javascript library
 *  - uupaa.js spin-off project
 *
 * @author Takao Obara <uupaa.js@gmail.com>
 * @license uupaa-color.js is licensed under the terms and conditions of the MIT licence.
 * @version 3.1
 * @date 2008-11-23
 * @see <a href="http://code.google.com/p/uupaa-js/">uupaa.js Home(Google Code)</a>
 * @see <a href="http://code.google.com/p/uupaa-js-spinoff/">uupaa.js SpinOff Project Home(Google Code)</a>
 * @see <a href="http://www.w3.org/TR/2003/CR-css3-color-20030514/">CSS3 Color Module</a>
 */
if (!uuClass.Color) {

/** Color
 *
 * @class
 */
uuClass.Color = function(r, g, b, a) {
  this.construct.apply(this, arguments);
};

// Color factory
uu.color = function(r, g, b, a) {
  return new uuClass.Color(r, g, b, a);
};

/** Color Dictionary
 *
 * @class Singleton
 */
uuClass.ColorDictionary = function() {
  var me = this, fn = arguments.callee;
  if (!fn.instance) {
    fn.instance = me; // keep instance
    me.construct.apply(me, arguments);
  }
  return fn.instance;
};

// --- local scope ------------------------------------------------------
(function() {
var _int = parseInt, _float = parseFloat, _round = Math.round,
    _C = uuClass.Color, _dict,

    // --- const ---
    COLOR_NAMES = "000000black,888888gray,ccccccsilver,ffffffwhite,ff0000red,ffff00yellow,00ff00lime,00ffffaqua,00ffffcyan,0000ffblue,ff00fffuchsia,ff00ffmagenta,"+
                  "880000maroon,888800olive,008800green,008888teal,000088navy,880088purple,696969dimgray,808080gray,a9a9a9darkgray,c0c0c0silver,d3d3d3lightgrey,"+
                  "dcdcdcgainsboro,f5f5f5whitesmoke,fffafasnow,708090slategray,778899lightslategray,b0c4delightsteelblue,4682b4steelblue,5f9ea0cadetblue,4b0082indigo,"+
                  "483d8bdarkslateblue,6a5acdslateblue,7b68eemediumslateblue,9370dbmediumpurple,f8f8ffghostwhite,00008bdarkblue,0000cdmediumblue,4169e1royalblue,"+
                  "1e90ffdodgerblue,6495edcornflowerblue,87cefalightskyblue,add8e6lightblue,f0f8ffaliceblue,191970midnightblue,00bfffdeepskyblue,87ceebskyblue,"+
                  "b0e0e6powderblue,2f4f4fdarkslategray,00ced1darkturquoise,afeeeepaleturquoise,f0ffffazure,008b8bdarkcyan,20b2aalightseagreen,48d1ccmediumturquoise,"+
                  "40e0d0turquoise,7fffd4aquamarine,e0fffflightcyan,00fa9amediumspringgreen,7cfc00lawngreen,00ff7fspringgreen,7fff00chartreuse,adff2fgreenyellow,"+
                  "2e8b57seagreen,3cb371mediumseagreen,66cdaamediumaquamarine,98fb98palegreen,f5fffamintcream,006400darkgreen,228b22forestgreen,32cd32limegreen,"+
                  "90ee90lightgreen,f0fff0honeydew,556b2fdarkolivegreen,6b8e23olivedrab,9acd32yellowgreen,8fbc8fdarkseagreen,9400d3darkviolet,8a2be2blueviolet,"+
                  "dda0ddplum,d8bfd8thistle,8b008bdarkmagenta,9932ccdarkorchid,ba55d3mediumorchid,da70d6orchid,ee82eeviolet,e6e6falavender,c71585mediumvioletred,"+
                  "bc8f8frosybrown,ff69b4hotpink,ffc0cbpink,ffe4e1mistyrose,ff1493deeppink,db7093palevioletred,e9967adarksalmon,ffb6c1lightpink,fff0f5lavenderblush,"+
                  "cd5c5cindianred,f08080lightcoral,f4a460sandybrown,fff5eeseashell,dc143ccrimson,ff6347tomato,ff7f50coral,fa8072salmon,ffa07alightsalmon,ffdab9peachpuff,"+
                  "ffffe0lightyellow,b22222firebrick,ff4500orangered,ff8c00darkorange,ffa500orange,ffd700gold,fafad2lightgoldenrodyellow,8b0000darkred,a52a2abrown,"+
                  "a0522dsienna,b8860bdarkgoldenrod,daa520goldenrod,deb887burlywood,f0e68ckhaki,fffacdlemonchiffon,d2691echocolate,cd853fperu,bdb76bdarkkhaki,bdb76btan,"+
                  "eee8aapalegoldenrod,f5f5dcbeige,ffdeadnavajowhite,ffe4b5moccasin,ffe4c4bisque,ffebcdblanchedalmond,ffefd5papayawhip,fff8dccornsilk,f5deb3wheat,"+
                  "faebd7antiquewhite,faf0e6linen,fdf5e6oldlace,fffaf0floralwhite,fffff0ivory",
    COLOR_PRESET = {
      ZERO:     { r: 0, g: 0, b: 0, a: 0 }, // Transparent Black
      BLACK:    { r: 0, g: 0, b: 0, a: 1 },
      WHITE:    { r: 255, g: 255, b: 255, a: 1 }
    },
    COLOR_STYLE = {
      RGBAHash: function(r) { return r; },
      HEX:      function(r) { var F = _dict.dec2hex;
                              return ["#", F[r.r], F[r.g], F[r.b]].join(""); },
      RGB:      function(r) { return "rgb(" + [r.r, r.g, r.b].join(",") + ")"; },
      RGBA:     function(r) { return "rgba(" + [r.r, r.g, r.b, r.a].join(",") + ")"; },
      HSVA:     function(r) { var h = _C._rgba2hsva(r);
                              return "hsva(" + [((h.h * 100) | 0) / 100,
                                                ((h.s * 100) | 0) / 100,
                                                ((h.v * 100) | 0) / 100, h.a].join(",") + ")"; },
      HSLA:     function(r) { var h = _C._rgba2hsla(r);
                              return "hsla(" + [((h.h * 100) | 0) / 100,
                                                ((h.s * 100) | 0) / 100,
                                                ((h.l * 100) | 0) / 100, h.a].join(",") + ")"; }
    },
    COLOR_HEX6_REGEX = /^#(([\da-f])([\da-f])([\da-f]))(([\da-f])([\da-f]{2}))?$/,
    COLOR_RGBA_REGEX = /(rgb[a]?)\s*\(\s*([0-9a-f\.%]+)\s*,\s*([0-9a-f\.%]+)\s*,\s*([0-9a-f\.%]+)(?:\s*,\s*([0-9\.]+))?\s*\)/,
    ARG_TYPES = { "number": 1, "string": 2 };

// --- uuClass.ColorDictionary methods ---
uuClass.ColorDictionary.prototype = {
  // uuClass.ColorDictionary.construct - create dictionary
  construct:
            function() {
              this.name2hash = { /* black: RGBAHEXHash, ... */ };
              this.dec2hex = Array(256); // DecNumber(255)  -> HexString("FF")
              this.hex2dec = Array(256); // HecString("FF") -> DecNumber(255)
              this.hexAlphaCache = { /* string: [ "#ffffff", alpha ], ... */ };
              this.hexAlphaCacheCount = 0;

              var i, j, iz, v, hex6, color, d, h, item = COLOR_NAMES.split(",");

              // create dec <-> hex table
              for (i = 0; i < 16; ++i) {
                for (j = 0; j < 16; ++j) {
                  d = i * 16 + j;
                  h = i.toString(16) + j.toString(16);
                  this.dec2hex[d] = h;
                  this.hex2dec[h] = d;
                }
              }

              // create color dictionary
              for (i = 0, iz = item.length; i < iz; ++i) {
                v = item[i];
                hex6 = v.substring(0, 6); // "000000"
                color = v.substring(6);   // "black"
                this.name2hash[color] = {
                  rgba: _C._toRGBAHash(_int(hex6, 16), 1),
                  hex: "#" + hex6
                };
              }
              this.name2hash["transparent"] = {
                rgba: _C._toRGBAHash(0, 0),
                hex: "#000000"
              };
            },

  // uuClass.ColorDictionary.addColor - add user definition color(s)
  addColor: function(colors) { // "name:color;name:color..."
              var c = (colors.toLowerCase().replace(/^[\s;]*|[\s;]*$/g, "")).split(";"),
                  v, rgba, i = 0, iz = c.length;
              for (; i < iz; ++i) {
                v = c[i].split(":");
                rgba = _C._hash(v[1]);
                this.name2hash[v[0]] = { rgba: rgba, hex: COLOR_STYLE.HEX(rgba) };
              }
            },
  clearCache:
            function(threshold /* = 1024 */) {
              if (this.hexAlphaCacheCount > threshold) {
                this.hexAlphaCache = {};
                this.hexAlphaCacheCount = 0;
              }
            }
};

// --- uuClass.Color methods ---
uuClass.Color.prototype = {
  // uuClass.Color.construct - create instance, push color stack
  construct:
            function(r, g /* = undefined */, b /* = undefined */, a /* = undefined */) {
              this._lock = 0;
              this._stack = [];
              this.push(r, g, b, a);
            },

  // uuClass.Color.push - push color stack
  //    r is uuClass.Color instance
  //      or RGBAHash or HSVAHash or Number or String("#FFF", "#FFFFFF", "rgb(...)", "rgba(...)")
  push:     function(r, g, b, a) {
              var rv, i, iz, c;
              if (r !== void 0) {
                if (r instanceof _C) { // stack copy
                  for (i = 0, iz = r._stack.length; i < iz; ++i) {
                    this._stack.push(uu.mix({}, r._stack[i])); // clone
                  }
                } else {
                  if (r instanceof Array) { // join array
                    for (i = 0, iz = r.length; i < iz; ++i) {
                      if ( (rv = _C._hash(r[i])) ) {
                        this._stack.push(rv);
                      }
                    }
                  } else if (typeof r === "string" && r.indexOf(";") > -1) { // "red;blue"
                    c = (r.toLowerCase().replace(/^[\s;]*|[\s;]*$/g, "")).split(";");
                    for (i = 0, iz = c.length; i < iz; ++i) {
                      if ( (rv = _C._hash(c[i])) ) {
                        this._stack.push(rv);
                      }
                    }
                  } else { // "#fff"
                    if ( (rv = _C._hash(r, g, b, a)) ) {
                      this._stack.push(rv);
                    }
                  }
                }
              }
              return this;
            },

  // uuClass.Color.pop - pop color stack and unlock
  pop:      function(colorStyle /* = "RGBAHash" */) {
              this.unlock();
              var style = colorStyle || "RGBAHash",
                  rv = this._stack.pop() || COLOR_PRESET.ZERO;
              return COLOR_STYLE[style](rv);
            },

  // uuClass.Color.clear - clear all color stack and unlock
  clear:    function() {
              this._stack = [];
              return this.unlock();
            },

  // uuClass.Color.size - get stack size
  size:     function() {
              return this._stack.length;
            },

  // uuClass.Color.empty - is Empty stack(every false)
  empty:    function() {
              return !this._stack.length;
            },

  // uuClass.Color.top - get top stack
  top:      function() {
              return this.ref(this._stack.length - 1);
            },

  // uuClass.Color.bottom - get bottom stack
  bottom:   function() {
              return this.ref(0);
            },

  // uuClass.Color.ref - refer to color stack
  ref:      function(n /* = undefined(top stack) */) {
              return this._stack[this._ref(n)] || COLOR_PRESET.ZERO;
            },
  _ref:     function(n) {
              if (n !== void 0) { return n; }
              if (this._lock) { return this._lock - 1; }
              if (this._stack.length) { return this._stack.length - 1; }
              return 0; // empty stack
            },

  // uuClass.Color.copy - copy top stack
  copy:     function(n /* = undefined(top stack) */) {
              this._stack.push(this.ref(n));
              return this;
            },

  // uuClass.Color.lock - lock refrence position
  lock:     function() {
              this._lock = this._stack.length;
              return this;
            },

  // uuClass.Color.unlock - unlock refrence position
  unlock:   function() {
              this._lock = 0;
              return this;
            },

  // uuClass.Color.forEach
  forEach:  function(fn, me, colorStyle /* = RGBAHash */) {
              var v, i = 0, iz = this._stack.length,
                  through = colorStyle === void 0,
                  method = COLOR_STYLE[colorStyle || "RGBAHash"];
              for(; i < iz; ++i) {
                v = through ? this._stack[i] : method(this._stack[i]);
                fn.call(me, v, i, this);
              }
              return this;
            },

  // uuClass.Color.hexEach - forEach + hex()
  hexEach:  function(fn, me) {
              return this.forEach(fn, me, "HEX");
            },

  // uuClass.Color.hexEach - forEach + rgba()
  rgbaEach: function(fn, me) {
              return this.forEach(fn, me, "RGBA");
            },

  // uuClass.Color.zero - push Transparent black( { r: 0, g: 0, b: 0, a: 0 } )
  zero:     function() {
              this._stack.push(COLOR_PRESET.ZERO);
              return this;
            },

  // uuClass.Color.black - push Black( { r: 0, g: 0, b: 0, a: 1 } )
  black:    function() {
              this._stack.push(COLOR_PRESET.BLACK);
              return this;
            },

  // uuClass.Color.white - push White( { r: 255, g: 255, b: 255, a: 1 } )
  white:    function() {
              this._stack.push(COLOR_PRESET.WHITE);
              return this;
            },

  // uuClass.Color.hex - return ColorString( "#ffffff" )
  hex:      function(n /* = undefined(top stack) */) {
              return COLOR_STYLE.HEX(this.ref(n));
            },

  // uuClass.Color.rgb - return ColorString( "rgb(0,0,0)" )
  rgb:      function(n /* = undefined(top stack) */) {
              return COLOR_STYLE.RGB(this.ref(n));
            },

  // uuClass.Color.rgba - return ColorString( "rgba(0,0,0,0)" )
  rgba:     function(n /* = undefined(top stack) */) {
              return COLOR_STYLE.RGBA(this.ref(n));
            },

  // uuClass.Color.hsva - return ColorString( "hsva(0,0,0,0)" )
  hsva:     function(n /* = undefined(top stack) */) {
              return COLOR_STYLE.HSVA(this.ref(n));
            },

  // uuClass.Color.hsla - return ColorString( "hsla(0,0,0,0)" )
  hsla:     function(n /* = undefined(top stack) */) {
              return COLOR_STYLE.HSLA(this.ref(n));
            },

  // uuClass.Color.toString - return hex()
  toString: function() {
              return this.hex();
            },

  // uuClass.Color.tone - push arrangemented color(Hue, Saturation and Value)
  tone:     function(h /* = 0 */, s /* = 0 */, v /* = 0 */) {
              if (!h && !s && !v) { return this.copy(); }
              var rv = _C._rgba2hsva(this.ref());
              rv.h += h, rv.h = (rv.h > 360) ? rv.h - 360 : (rv.h < 0) ? rv.h + 360 : rv.h;
              rv.s += s, rv.s = (rv.s > 100) ? 100 : (rv.s < 0) ? 0 : rv.s;
              rv.v += v, rv.v = (rv.v > 100) ? 100 : (rv.v < 0) ? 0 : rv.v;
              this._stack.push(_C._hsva2rgba(rv));
              return this;
            },

  // uuClass.Color.complementary - push complementary-color
  complementary:
            function() {
              var r = this.ref();
              this._stack.push({ r: r.r ^ 0xff, g: r.g ^ 0xff, b: r.b ^ 0xff, a: r.a });
              return this;
            },

  // uuClass.Color.gray - push gray-color
  gray:     function() {
              var r = this.ref();
              this._stack.push({ r: r.g, g: r.g, b: r.g, a: r.a }); // G channel method
              return this;
            },

  // uuClass.Color.sepia - push sepia-color
  sepia:    function() {
              var rgba = this.ref(), rv,
                  r = rgba.r, g = rgba.g, b = rgba.b,
                  y = 0.2990 * r + 0.5870 * g + 0.1140 * b, u = -0.091, v = 0.056;

              r = y              + 1.4026 * v;
              g = y - 0.3444 * u - 0.7114 * v;
              b = y + 1.7330 * u             ;

              r *= 1.2;
              b *= 0.8;

              r = r < 0 ? 0 : r > 255 ? 255 : r;
              g = g < 0 ? 0 : g > 255 ? 255 : g;
              b = b < 0 ? 0 : b > 255 ? 255 : b;

              rv = { r: r | 0, g: g | 0, b: b | 0, a: rgba.a };
              this._stack.push(rv);
              return this;
            }
};

// --- uuClass.Color static functions ---
uu.mix(uuClass.Color, {
  _toDec:   function(n) { // n = string("12" or "12%"), "12" -> 12, "12%" -> 30
              var rv = (n.lastIndexOf("%") === -1) ? _int(n) : (_float(n) * 255 / 100) | 0;
              return rv > 255 ? 255 : rv;
            },

  _toRGBAHash:
            function(n, a) { // n = 16777215 ～ 0, a = 1.0 ～ 0.0
              return { r: (n >> 16) & 0xff, g: (n >> 8) & 0xff, b: n & 0xff, a: a };
            },

  _hash:    function(r, g, b, a) {
              if (r === void 0 || r === null) { return 0; }

              var m, type = ARG_TYPES[typeof r] || 0,
                  n2h = _dict.name2hash, h2d = _dict.hex2dec, _toDec = _C._toDec;

              if (type === 1) {
                return (g === void 0) ? this._toRGBAHash(r, 1) : { r: r, g: g, b: b, a: (a === void 0) ? 1 : a };
              } else if (type === 2) {
                r = r.toLowerCase();
                if (r in n2h) { // named color(r = "violet" or "usercolor")
                  return n2h[r].rgba;
                }
                if ( (m = r.match(COLOR_HEX6_REGEX)) ) { // #fff or #ffffff
                  return (r.length > 4) ? { r: h2d[m[2] + m[3]], g: h2d[m[4] + m[6]], b: h2d[m[7]], a: 1 }
                                        : { r: h2d[m[2] + m[2]], g: h2d[m[3] + m[3]], b: h2d[m[4] + m[4]], a: 1 };
                }
                if ( (m = r.match(COLOR_RGBA_REGEX)) ) { // rgb() or rgba()
                  return { r: _toDec(m[2]), g: _toDec(m[3]), b: _toDec(m[4]), a: m[1] === "rgba" ? _float(m[5]) : 1 };
                }
              } else {
                if ("r" in r) { return uu.mix({}, r); } // RGBAHash clone
                if ("v" in r) { return _C._hsva2rgba(r); } // HSVAHash to RGBAHash
                if ("l" in r) { return _C._hsla2rgba(r); } // HSLAHash to RGBAHash
              }
              return 0; // invalid color
            },

  _hexAlpha:
            function(color) {
              var rv, c, m, n2h, d2h, _toDec, cache = _dict.hexAlphaCache, _d = _dict;

              if ((ARG_TYPES[typeof color] || 0) === 2) {
                if (color in cache) { // search cache(case-sensitive)
                  return cache[color];
                }
                n2h = _d.name2hash;
                c = color.toLowerCase();
                if (c in n2h) { // search named color(c = "violet" or "usercolor")(case-insensitive)
                  m = n2h[c];
                  rv = [m.hex, m.rgba.a];
                } else if ( (m = c.match(COLOR_HEX6_REGEX)) ) { // #fff or #ffffff
                  rv = (c.length > 4) ? [c, 1]
                                      : [["#", m[2], m[2], m[3], m[3], m[4], m[4]].join(""), 1];
                } else if ( (m = c.match(COLOR_RGBA_REGEX)) ) { // rgb() or rgba()
                  d2h = _d.dec2hex, _toDec = _C._toDec;
                  rv = [["#", d2h[_toDec(m[2])], d2h[_toDec(m[3])], d2h[_toDec(m[4])]].join(""),
                        m[1] === "rgba" ? _float(m[5]) : 1];
                } else {
                  rv = ["#000000", 0];
                }
                _d.hexAlphaCache[color] = rv; // add cache(case-sensitive)
                ++_d.hexAlphaCacheCount;
              } else {
                rv = ["#000000", 0];
              }
              return rv;
            },
  _rgba2hsva:
            function(rgba) {
              var r = rgba.r / 255, g = rgba.g / 255, b = rgba.b / 255,
                  max = Math.max(r, g, b), diff = max - Math.min(r, g, b),
                  h = 0, s = max ? _round(diff / max * 100) : 0, v = _round(max * 100);
              if (!s) { return { h: 0, s: 0, v: v, a: rgba.a }; }

              h = (r === max) ? ((g - b) * 60 / diff) :
                  (g === max) ? ((b - r) * 60 / diff + 120)
                              : ((r - g) * 60 / diff + 240);
              // HSVAHash( { h:360, s:100, v:100, a:1.0 } )
              return { h: (h < 0) ? h + 360 : h, s: s, v: v, a: rgba.a };
            },

  _hsva2rgba:
            function(hsva) {
              var h = (hsva.h === 360) ? 0 : hsva.h, s = hsva.s / 100, v = hsva.v / 100, a = hsva.a,
                  h60 = h / 60, matrix = h60 | 0, f = h60 - matrix;
              if (!s) { h = _round(v * 255); return { r: h, g: h, b: h, a: a }; }

              function MATRIX() {
                var _r = _round,
                    p = _r((1 - s) * v * 255),
                    q = _r((1 - (s * f)) * v * 255),
                    t = _r((1 - (s * (1 - f))) * v * 255),
                    w = _r(v * 255);
                switch (matrix) {
                  case 0: return { r: w, g: t, b: p, a: a };
                  case 1: return { r: q, g: w, b: p, a: a };
                  case 2: return { r: p, g: w, b: t, a: a };
                  case 3: return { r: p, g: q, b: w, a: a };
                  case 4: return { r: t, g: p, b: w, a: a };
                  case 5: return { r: w, g: p, b: q, a: a };
                }
                return { r: 0, g: 0, b: 0, a: a };
              }
              return MATRIX(); // RGBAHash( { r: 255, g: 255, b: 255, a: 1 } )
            },

  _rgba2hsla:
            function(rgba) {
              var r = rgba.r / 255, g = rgba.g / 255, b = rgba.b / 255,
                  max = Math.max(r, g, b), min = Math.min(r, g, b),
                  diff = max - min,
                  h = 0, s = 0, l = (min + max) / 2;
              if (l > 0 && l < 1) {
                s = diff / (l < 0.5 ? l * 2 : 2 - (l * 2));
              }
              if (diff > 0) {
                if (max === r && max !== g) {
                  h += (g - b) / diff;
                } else if (max === g && max !== b) {
                  h += (b - r) / diff + 2;
                } else if (max === b && max !== r) {
                  h += (r - g) / diff + 4;
                }
                h *= 60;
              }
              return { h: h, s: _round(s * 100), l: _round(l * 100), a: rgba.a };
            },

  _hsla2rgba:
            function(hsla) { // h: 0-360, s: 0-100, l: 0-100
              var h = (hsla.h === 360) ? 0 : hsla.h, s = hsla.s / 100, l = hsla.l / 100,
                  r, g, b, s1, s2, l1, l2;
              if (h < 120) {
                r = (120 - h) / 60, g = h / 60, b = 0;
              } else if (h < 240) {
                r = 0, g = (240 - h) / 60, b = (h - 120) / 60;
              } else {
                r = (h - 240) / 60, g = 0, b = (360 - h) / 60;
              }
              s1 = 1 - s;
              s2 = s * 2;

              r = s2 * (r > 1 ? 1 : r) + s1;
              g = s2 * (g > 1 ? 1 : g) + s1;
              b = s2 * (b > 1 ? 1 : b) + s1;

              if (l < 0.5) {
                r *= l, g *= l, b *= l;
              } else {
                l1 = 1 - l;
                l2 = l * 2 - 1;
                r = l1 * r + l2;
                g = l1 * g + l2;
                b = l1 * b + l2;
              }
              return { r: _round(r * 255), g: _round(g * 255), b: _round(b * 255), a: hsla.a };
            }
});

// init
_dict = new uuClass.ColorDictionary();

})(); // end (function())() scope

} // if (!uuClass.Color)

