constructors/canvas-draw.js

module.exports = function (canvas, _config) {
	/**
 	* create an environment for canvas drawing
 	*/
	if (!canvas) {
		canvas  = document.createElement('canvas');
	}
	var context2d   = canvas.getContext('2d');
	var context	 = context2d;

	var now;

	if (window.performance && typeof(window.performance.now) === "function") {
		now = function () {
			return window.performance.now();
		};
	} else if (typeof(Date.now) === "function") {
		return Date.now();
	} else {
		now = function () {
			return new Date().valueOf();
		};
	}

	var app = new ApplicationPrototype();
	var keyGenerator= ((function () {
		var i = 0;
		return function () {
			i++;
			return i.toString(36) + '_' + Math.floor(Math.random() * 1000000000).toString(36);
		};
	})());
	_config = _config || {};
	var config      = {
		fps   : _config.fps || 40,
		fpsInterval : 1000 / ( _config.fps || 40 ),
		debug : _config.debug || false,
		animationFlags : {
			startTime: null,
			now: null,
			then: null,
			elapsed: null,
			drawing: false
		}
	};

	var resource	= {
		/**
	 	* set of canvas paths
	 	* @type {Array}
	 	*/
		paths   : [],
		animationStatus : false,
		animationRequest: false
	};

	app.debug = function (status) {
		if (typeof(status) !== "undefined") {
			config.debug = !!status;
		}
		return config.debug;
	};

	app.bind("context", function () {
		return context;
	}, "");
	app.bind("paths", function () {
		return resource.paths;
	}, "");
	app.bind("createPath", function (conf) {
		/**
		* createPath - create a generic path object
		* @return {pathObject}
		*/
		var path = new ApplicationPrototype();
		var config  = {
			id	  : keyGenerator(),
			isReady : true,
			groups  : [],
			coords  : [],
			vars    : {},
			operations  : []
		};
		((function () {
			var i;
			for (i in conf) {
				config[i]   = conf[i];
			}
		})());

		if (!Array.isArray(config.operations)) config.operations = [];

		config.operations = config.operations.map(function (operation) {
			if (!operation) return null;

			if (typeof(operation) !== "object") return null;

			if (!operation.operation) return null;

			if (typeof(operation.group) !== "string") operation.group = "";

			operation.id = operation.id || keyGenerator();

			return operation;
		}).filter(function (operation) {
			return !!operation;
		});

		path.bind("app", function () {
			return app;
		}, "");
		path.bind("config", function () {
			return config;
		}, "");
		path.bind("vars", function () {
			return config.vars;
		}, "");
		path.bind("groups", function () {
			return config.groups;
		}, "");
		path.bind("operationsRemoveByGroup", function (group) {
			config.operations = config.operations.filter(function (operation) {
				return operation.group !== group;
			});
			return config.operations;
		}, "");
		path.bind("operationsRemoveById", function (key) {
			config.operations = config.operations.filter(function (operation) {
				return operation.id !== key;
			});
			return config.operations;
		}, "");
		path.bind("operationsRemoveByKey", function (key) {
			console.warn("path.operationsRemoveByKey is deprecated from 2016.10.05");
			return path.operationsRemoveById(key);
		});
		path.bind("operationById", function (id) {
			return config.operations.filter(function (operation) {
				return operation.id === id;
			})[0];
		}, "");
		path.bind("operationsByGroup", function (group) {
			return config.operations.filter(function (operation) {
				return operation.group === group;
			});
		}, "");
		path.bind("operations", function (operation, params, key, group) {
			var i;
			var index = false;
			for (i=0;i<config.operations.length;i++) {
				if (config.operations[i].id === key) {
					index   = i;
				}
			}
			if (index !== false) {
				config.operations[index].operation  = operation || config.operations[index].operation;
				config.operations[index].params	 = params || config.operations[index].params;
				config.operations[index].group	  = group || config.operations[index].group;
				return path;
			} else {
				config.operations.push({
					operation   : operation,
					params	  : (params || []),
					id	 : key || keyGenerator(),
					group	   : group || ""
				});
				return path;
			}
		}, "");
		path.bind("coords", function (x,y) {
			if (typeof(x) === "number" && typeof(y) === "number") {
				var status = false;
				config.coords.forEach(function (coords) {
					if (coords.length === 4) {
						if (
							x >= coords[0]
							&&
							x <= coords[2] + 0.00000001
							&&
							y >= coords[1]
							&&
							y <= coords[3] + 0.00000001
						) {
							status = true;
						}
					}
				});
				return status;
			}
			return config.coords;
		}, "");
		path.bind("group", function (name, val) {
			if (typeof(val) === "undefined") {
				return config.groups.indexOf(name) !== -1;
			} else {
				if (val) {
					if (config.groups.indexOf(name) === -1) {
						config.groups.push(name);
					}
				} else {
					if (config.groups.indexOf(name) !== -1) {
						config.groups   = config.groups.filter(function (group) {
							return group !== name;
						});
					}
				}
				return path;
			}
		}, "");
		path.isReady = function (bool) {
			path.emit("onIsReady", [bool]);

			if (typeof(bool) !== "undefined") {
				config.isReady  = !!bool;
			}
			return config.isReady;
		};
		path.render = function (cb) {
			path.emit("onRender", []);
			var context = app.context();
			context.save();
			var er;
			var debug = app.debug();
			config.operations.forEach(function (operation) {
				try {
					var params = typeof(operation.params) === "function" ? (
						operation.params(path, operation, app)
					) : operation.params;
					if (typeof(operation.operation) === "function") {
						if (debug) {
							console.log("Operation: operations[" + operation.id + "](", (
								typeof(operation.params) === "function" ? (
									"@Function"
								) : ""
							), params + ")");
						}
						operation.operation.apply(
							path,
							params || []
						);
					} else if (typeof(context[operation.operation]) === "function") {
						if (debug) {
							console.log("Operation: Context." + operation.operation + "(", (
								typeof(operation.params) === "function" ? (
									"@Function"
								) : ""
							), params, ")");
						}
						context[operation.operation].apply(
							context,
							params || []
						);
					} else {
						if (debug) {
							console.log("Operation: Context." + operation.operation + " = ", (
								typeof(operation.params) === "function" ? (
									"@Function"
								) : ""
							), params);
						}
						context[operation.operation]	= params;
					}
				} catch (er) {
					console.error(er);
				}
			});
			context.restore();
			if (cb) cb();
		};
		app.emit("path-created", [path]);
		return path;
	}, "on");
	app.bind("path", function (conf) {
		var path = app.createPath(conf);
		resource.paths.push(path);
		return path;
	}, "");
	app.bind("pathById", function (id) {
		return resource.paths.filter(function (path) {
			return path.config().id === id;
		})[0];
	}, "");
	app.bind("group", function (name) {
		return resource.paths.filter(function (path) {
			return path.groups().indexOf(name) !== -1;
		});
	}, "");
	app.bind("imageSmoothingEnabled", function (bool) {
		if (typeof(bool) !== "undefined") {
			config.smoothing	= !!bool;
			[
				app.context()
			].forEach(function (context) {
				context.imageSmoothingEnabled	   = config.smoothing;
				context.mozImageSmoothingEnabled	= config.smoothing;
				context.webkitImageSmoothingEnabled = config.smoothing;
				context.msImageSmoothingEnabled	 = config.smoothing;
			});
		}
		return config.smoothing;
	}, "on");

	// TODO
	// ctx.fillStyle = "rgb(200,0,0)";
	// // sets the color to fill in the rectangle with
	// ctx.fillRect(10, 10, 55, 50);
	// // draws the rectangle at position 10, 10 with a width of 55 and a height of 50
	// CanvasRenderingContext2D.clearRect()
	// CanvasRenderingContext2D.fillRect()
	// CanvasRenderingContext2D.strokeRect()
	// CanvasRenderingContext2D.fillText()
	// CanvasRenderingContext2D.strokeText()
	// CanvasRenderingContext2D.measureText()
	// createCircle

	if (canvas.attrdata) {
		canvas.attrdata.CanvasDraw = app;
	}

	app.bind("canvas", function () {
		return canvas;
	}, "");
	app.bind("height", function (value) {
		if (typeof(value) === "number") {
			if (parseInt(canvas.height) !== parseInt(value)) {
				canvas.height = value;
				app.render();
			}
		}
		return canvas.height;
	}, "");
	app.bind("width", function (value) {
		if (typeof(value) === "number") {
			if (parseInt(canvas.width) !== parseInt(value)) {
				canvas.width = value;
				app.render();
			}
		}
		return canvas.width;
	}, "");

	app.render = function (cb) {
		app.emit("onRender", []);
		var index = 0;
		var _tick = function () {
			var path = resource.paths[index++];

			if (!path) {
				if (cb) cb();
				return;
			}

			if (path.isReady()) {
				path.render(_tick);
			} else {
				_tick();
			}
		};

		_tick();
	};


	var animateRequest = function () {
		resource.animationRequest	= requestAnimationFrame(function () {
			animate(resource.animationStatus);
			resource.animationRequest	= null;
		});
	};
	var animateRender = function () {
		config.animationFlags.drawing = false;

		animateRequest();
	};
	var animate = function (state) {
		if (state === undefined) {
			return resource.animationStatus || false;
		}

		var time = now();

		if (typeof(state) === "boolean") {
			if (resource.animationRequest) {
				var er;
				try {
					cancelAnimationFrame(resource.animationRequest);
				} catch (er) {
					console.error(er);
				}
			}

			if (state && !resource.animationStatus) {
				config.animationFlags.then = time;

				resource.animationStatus = state;

				animateRequest();
				return;
			}

			resource.animationStatus = state;
		}

		

		if (state === true) {
			resource.animationStatus = state;
			
			config.animationFlags.now = time;
			config.animationFlags.elapsed = config.animationFlags.now - config.animationFlags.then;
			
			if (config.animationFlags.elapsed > config.fpsInterval) {
				config.animationFlags.then = config.animationFlags.now - (
					config.animationFlags.elapsed % config.fpsInterval
				);
				if (!config.animationFlags.drawing) {
					config.animationFlags.drawing = true;
					app.render(animateRender);
				} else {
					animate(resource.animationStatus);
				}
			} else {
				animateRequest();
			}
		}
	};

	app.bind("animate", animate);

	app.bind("fps", function (fps) {
		if (typeof(fps) === "number") {
			config.fps = Math.max(0.01, fps);
			config.fpsInterval = 1000 / config.fps;
		}
	});

	if (_config.animate || _config.animate === undefined) {
		animate(true);
	}

	return app;
};