1const conversions = require('./conversions');
2
3/*
4	This function routes a model to all other models.
5
6	all functions that are routed have a property `.conversion` attached
7	to the returned synthetic function. This property is an array
8	of strings, each with the steps in between the 'from' and 'to'
9	color models (inclusive).
10
11	conversions that are not possible simply are not included.
12*/
13
14function buildGraph() {
15	const graph = {};
16	// https://jsperf.com/object-keys-vs-for-in-with-closure/3
17	const models = Object.keys(conversions);
18
19	for (let len = models.length, i = 0; i < len; i++) {
20		graph[models[i]] = {
21			// http://jsperf.com/1-vs-infinity
22			// micro-opt, but this is simple.
23			distance: -1,
24			parent: null
25		};
26	}
27
28	return graph;
29}
30
31// https://en.wikipedia.org/wiki/Breadth-first_search
32function deriveBFS(fromModel) {
33	const graph = buildGraph();
34	const queue = [fromModel]; // Unshift -> queue -> pop
35
36	graph[fromModel].distance = 0;
37
38	while (queue.length) {
39		const current = queue.pop();
40		const adjacents = Object.keys(conversions[current]);
41
42		for (let len = adjacents.length, i = 0; i < len; i++) {
43			const adjacent = adjacents[i];
44			const node = graph[adjacent];
45
46			if (node.distance === -1) {
47				node.distance = graph[current].distance + 1;
48				node.parent = current;
49				queue.unshift(adjacent);
50			}
51		}
52	}
53
54	return graph;
55}
56
57function link(from, to) {
58	return function (args) {
59		return to(from(args));
60	};
61}
62
63function wrapConversion(toModel, graph) {
64	const path = [graph[toModel].parent, toModel];
65	let fn = conversions[graph[toModel].parent][toModel];
66
67	let cur = graph[toModel].parent;
68	while (graph[cur].parent) {
69		path.unshift(graph[cur].parent);
70		fn = link(conversions[graph[cur].parent][cur], fn);
71		cur = graph[cur].parent;
72	}
73
74	fn.conversion = path;
75	return fn;
76}
77
78module.exports = function (fromModel) {
79	const graph = deriveBFS(fromModel);
80	const conversion = {};
81
82	const models = Object.keys(graph);
83	for (let len = models.length, i = 0; i < len; i++) {
84		const toModel = models[i];
85		const node = graph[toModel];
86
87		if (node.parent === null) {
88			// No possible conversion, or this node is the source model.
89			continue;
90		}
91
92		conversion[toModel] = wrapConversion(toModel, graph);
93	}
94
95	return conversion;
96};
97
98