1 /*global one*/
  2 one.include('js:one/color.js');
  3 one.include('js:String-capitalize.js');
  4 
  5 /*jslint evil:true*/
  6 
  7 one.color.installedColorSpaces = [];
  8 
  9 one.color.installColorSpace = function (colorSpaceName, propertyDefinitions, config) {
 10     var propertyNames = propertyDefinitions.map(function (propertyDefinition) {
 11             return propertyDefinition.match(/[A-Z]/)[0].toLowerCase();
 12         }),
 13         longPropertyNames = propertyDefinitions.map(function (propertyDefinition) {
 14             return propertyDefinition.toLowerCase().capitalize();
 15         }),
 16         Constructor = one.color[colorSpaceName] = new Function(propertyNames.join(","),
 17             // Allow passing an array to the constructor:
 18             "if (Object.prototype.toString.apply(" + propertyNames[0] + ") === '[object Array]') {" +
 19                 propertyNames.map(function (propertyName, i) {
 20                     return propertyName + "=" + propertyNames[0] + "[" + i + "];";
 21                 }).reverse().join("") +
 22             "}" +
 23             "if (" + propertyNames.filter(function (propertyName) {
 24                 return propertyName !== 'a';
 25             }).map(function (propertyName) {
 26                 return "isNaN(" + propertyName + ")";
 27             }).join("||") + "){" + "throw \"[one.color." + colorSpaceName + "]: Invalid color: (\"+" + propertyNames.join("+\",\"+") + "+\")\";}" +
 28             propertyNames.map(function (propertyName) {
 29                 if (propertyName === 'h') {
 30                     return "this.h=h<0?h-Math.floor(h):h%1"; // Wrap
 31                 } else if (propertyName === 'a') {
 32                     return "this.a=(isNaN(a)||a>1)?1:(a<0?0:a);";
 33                 } else {
 34                     return "this." + propertyName + "=" + propertyName + "<0?0:(" + propertyName + ">1?1:" + propertyName + ")";
 35                 }
 36             }).join(";") + ";"
 37         ),
 38         prototype = Constructor.prototype;
 39 
 40     Constructor.propertyNames = propertyNames;
 41     Constructor.longPropertyNames = longPropertyNames;
 42 
 43     ['valueOf', 'toHex', 'toCSS', 'toCSSWithAlpha'].forEach(function (methodName) {
 44         prototype[methodName] = prototype[methodName] || (colorSpaceName === 'RGB' ? prototype.toHex : new Function("return this.toRGB()." + methodName + "();"));
 45     });
 46 
 47     prototype.isColor = true;
 48 
 49     prototype.equals = function (otherColor, epsilon) {
 50         if (typeof epsilon === 'undefined') {
 51             epsilon = 1e-10;
 52         }
 53 
 54         otherColor = otherColor['to' + colorSpaceName]();
 55 
 56         for (var i = 0; i < propertyNames.length; i = i + 1) {
 57             if (Math.abs(this[propertyNames[i]] - otherColor[propertyNames[i]]) > epsilon) {
 58                 return false;
 59             }
 60         }
 61 
 62         return true;
 63     };
 64 
 65     prototype.toJSON = new Function(
 66         "return ['" + colorSpaceName + "', " +
 67             propertyNames.map(function (propertyName) {
 68                 return "this." + propertyName;
 69             }, this).join(", ") +
 70         "];"
 71     );
 72 
 73     if (config.fromRGB) {
 74         one.color.RGB.prototype['to' + colorSpaceName] = config.fromRGB;
 75         delete config.fromRGB;
 76     }
 77     for (var prop in config) {
 78         if (config.hasOwnProperty(prop)) {
 79             prototype[prop] = config[prop];
 80         }
 81     }
 82 
 83     // It is pretty easy to implement the conversion to the same color space:
 84     prototype['to' + colorSpaceName] = function () {
 85         return this;
 86     };
 87     prototype.toString = new Function("return \"[one.color." + colorSpaceName + ":\"+" + propertyNames.map(function (propertyName, i) {
 88         return "\" " + longPropertyNames[i] + "=\"+this." + propertyName;
 89     }).join("+") + "+\"]\";");
 90 
 91     // Generate getters and setters
 92     propertyNames.forEach(function (propertyName, i) {
 93         var longPropertyName = longPropertyNames[i];
 94         prototype['get' + longPropertyName] = new Function("return this." + propertyName + ";");
 95         prototype['set' + longPropertyName] = new Function("newValue", "return new this.constructor(" + propertyNames.map(function (otherPropertyName, i) {
 96             return propertyName === otherPropertyName ? "newValue" : "this." + otherPropertyName;
 97         }).join(", ") + ");");
 98         prototype['adjust' + longPropertyName] = new Function("delta", "return new this.constructor(" + propertyNames.map(function (otherPropertyName, i) {
 99             return "this." + otherPropertyName + (propertyName === otherPropertyName ? "+delta" : "");
100         }).join(", ") + ");");
101     });
102 
103     function installForeignMethods(targetColorSpaceName, sourceColorSpaceName) {
104         var obj = {};
105         obj['to' + sourceColorSpaceName] = new Function("return this.toRGB().to" + sourceColorSpaceName + "();"); // Fallback
106         one.color[sourceColorSpaceName].propertyNames.forEach(function (property, i) {
107             var longPropertyName = one.color[sourceColorSpaceName].longPropertyNames[i];
108             obj['get' + longPropertyName] = new Function("return this.to" + sourceColorSpaceName + "().get" + longPropertyName + "();");
109             obj['set' + longPropertyName] = new Function("newValue", "return this.to" + sourceColorSpaceName + "().set" + longPropertyName + "(newValue);");
110             obj['adjust' + longPropertyName] = new Function("delta", "return this.to" + sourceColorSpaceName + "().adjust" + longPropertyName + "(delta);");
111         });
112         for (var prop in obj) {
113             if (obj.hasOwnProperty(prop) && one.color[targetColorSpaceName].prototype[prop] === undefined) {
114                 one.color[targetColorSpaceName].prototype[prop] = obj[prop];
115             }
116         }
117     }
118 
119     one.color.installedColorSpaces.forEach(function (otherColorSpaceName) {
120         installForeignMethods(colorSpaceName, otherColorSpaceName);
121         installForeignMethods(otherColorSpaceName, colorSpaceName);
122     });
123 
124     one.color.installedColorSpaces.push(colorSpaceName);
125 };
126