text: import { makeSVGGroup } from '../utils/draw';
  import { makeText, makePath, xLine, yLine, yMarker, yRegion, datasetBar, datasetDot, percentageBar, getPaths, heatSquare } from '../utils/draw';
  import { equilizeNoOfElements } from '../utils/draw-utils';
  import { translateHoriLine, translateVertLine, animateRegion, animateBar,
  	animateDot, animatePath, animatePathStr } from '../utils/animate';
  import { getMonthName } from '../utils/date-utils';
  import {resolveBarColor} from "../utils/colors";
  
  class ChartComponent {
  	constructor({
  					layerClass = '',
  					layerTransform = '',
  					constants,
  
  					getData,
  					makeElements,
  					animateElements
  				}) {
  		this.layerTransform = layerTransform;
  		this.constants = constants;
  
  		this.makeElements = makeElements;
  		this.getData = getData;
  
  		this.animateElements = animateElements;
  
  		this.store = [];
  		this.labels = [];
  
  		this.layerClass = layerClass;
  		this.layerClass = typeof(this.layerClass) === 'function'
  			? this.layerClass() : this.layerClass;
  
  		this.refresh();
  	}
  
  	refresh(data) {
  		this.data = data || this.getData();
  	}
  
  	setup(parent) {
  		this.layer = makeSVGGroup(this.layerClass, this.layerTransform, parent);
  	}
  
  	make() {
  		this.render(this.data);
  		this.oldData = this.data;
  	}
  
  	render(data) {
  		this.store = this.makeElements(data);
  
  		this.layer.textContent = '';
  		this.store.forEach(element => {
  			this.layer.appendChild(element);
  		});
  		this.labels.forEach(element => {
  			this.layer.appendChild(element);
  		});
  	}
  
  	update(animate = true) {
  		this.refresh();
  		let animateElements = [];
  		if(animate) {
  			animateElements = this.animateElements(this.data) || [];
  		}
  		return animateElements;
  	}
  }
  
  let componentConfigs = {
  	donutSlices: {
  		layerClass: 'donut-slices',
  		makeElements(data) {
  			return data.sliceStrings.map((s, i) => {
  				let slice = makePath(s, 'donut-path', data.colors[i], 'none', data.strokeWidth);
  				slice.style.transition = 'transform .3s;';
  				return slice;
  			});
  		},
  
  		animateElements(newData) {
  			return this.store.map((slice, i) => animatePathStr(slice, newData.sliceStrings[i]));
  		},
  	},
  	pieSlices: {
  		layerClass: 'pie-slices',
  		makeElements(data) {
  			return data.sliceStrings.map((s, i) =>{
  				let slice = makePath(s, 'pie-path', 'none', data.colors[i]);
  				slice.style.transition = 'transform .3s;';
  				return slice;
  			});
  		},
  
  		animateElements(newData) {
  			return this.store.map((slice, i) =>
  				animatePathStr(slice, newData.sliceStrings[i])
  			);
  		}
  	},
  	percentageBars: {
  		layerClass: 'percentage-bars',
  		makeElements(data) {
  			return data.xPositions.map((x, i) =>{
  				let y = 0;
  				let bar = percentageBar(x, y, data.widths[i],
  					this.constants.barHeight, this.constants.barDepth, data.colors[i]);
  				return bar;
  			});
  		},
  
  		animateElements(newData) {
  			if(newData) return [];
  		}
  	},
  	yAxis: {
  		layerClass: 'y axis',
  		makeElements(data) {
  			return data.positions.map((position, i) =>
  				yLine(position, data.labels[i] + (window.percentages ? '%' : ''), this.constants.width,
  					{mode: this.constants.mode, pos: this.constants.pos, shortenNumbers: this.constants.shortenNumbers})
  			);
  		},
  
  		animateElements(newData) {
  			let newPos = newData.positions;
  			let newLabels = newData.labels;
  			let oldPos = this.oldData.positions;
  			let oldLabels = this.oldData.labels;
  
  			[oldPos, newPos] = equilizeNoOfElements(oldPos, newPos);
  			[oldLabels, newLabels] = equilizeNoOfElements(oldLabels, newLabels);
  
  			this.render({
  				positions: oldPos,
  				labels: newLabels
  			});
  
  			return this.store.map((line, i) => {
  				return translateHoriLine(
  					line, newPos[i], oldPos[i]
  				);
  			});
  		}
  	},
  
  	xAxis: {
  		layerClass: 'x axis',
  		makeElements(data) {
  			return data.positions.map((position, i) =>
  				xLine(position, data.calcLabels[i], this.constants.height,
  					{mode: this.constants.mode, pos: this.constants.pos})
  			);
  		},
  
  		animateElements(newData) {
  			let newPos = newData.positions;
  			let newLabels = newData.calcLabels;
  			let oldPos = this.oldData.positions;
  			let oldLabels = this.oldData.calcLabels;
  
  			[oldPos, newPos] = equilizeNoOfElements(oldPos, newPos);
  			[oldLabels, newLabels] = equilizeNoOfElements(oldLabels, newLabels);
  
  			this.render({
  				positions: oldPos,
  				calcLabels: newLabels
  			});
  
  			return this.store.map((line, i) => {
  				return translateVertLine(
  					line, newPos[i], oldPos[i]
  				);
  			});
  		}
  	},
  
  	yMarkers: {
  		layerClass: 'y-markers',
  		makeElements(data) {
  			return data.map(m =>
  				yMarker(m.position, m.label, this.constants.width,
  					{labelPos: m.options.labelPos, mode: 'span', lineType: 'dashed'})
  			);
  		},
  		animateElements(newData) {
  			[this.oldData, newData] = equilizeNoOfElements(this.oldData, newData);
  
  			let newPos = newData.map(d => d.position);
  			let newLabels = newData.map(d => d.label);
  			let newOptions = newData.map(d => d.options);
  
  			let oldPos = this.oldData.map(d => d.position);
  
  			this.render(oldPos.map((pos, i) => {
  				return {
  					position: oldPos[i],
  					label: newLabels[i],
  					options: newOptions[i]
  				};
  			}));
  
  			return this.store.map((line, i) => {
  				return translateHoriLine(
  					line, newPos[i], oldPos[i]
  				);
  			});
  		}
  	},
  
  	yRegions: {
  		layerClass: 'y-regions',
  		makeElements(data) {
  			return data.map(r =>
  				yRegion(r.startPos, r.endPos, this.constants.width,
  					r.label, {labelPos: r.options.labelPos})
  			);
  		},
  		animateElements(newData) {
  			[this.oldData, newData] = equilizeNoOfElements(this.oldData, newData);
  
  			let newPos = newData.map(d => d.endPos);
  			let newLabels = newData.map(d => d.label);
  			let newStarts = newData.map(d => d.startPos);
  			let newOptions = newData.map(d => d.options);
  
  			let oldPos = this.oldData.map(d => d.endPos);
  			let oldStarts = this.oldData.map(d => d.startPos);
  
  			this.render(oldPos.map((pos, i) => {
  				return {
  					startPos: oldStarts[i],
  					endPos: oldPos[i],
  					label: newLabels[i],
  					options: newOptions[i]
  				};
  			}));
  
  			let animateElements = [];
  
  			this.store.map((rectGroup, i) => {
  				animateElements = animateElements.concat(animateRegion(
  					rectGroup, newStarts[i], newPos[i], oldPos[i]
  				));
  			});
  
  			return animateElements;
  		}
  	},
  
  	heatDomain: {
  		layerClass: function() { return 'heat-domain domain-' + this.constants.index; },
  		makeElements(data) {
  			let {index, colWidth, rowHeight, squareSize, radius, xTranslate} = this.constants;
  			let monthNameHeight = -12;
  			let x = xTranslate, y = 0;
  
  			this.serializedSubDomains = [];
  
  			data.cols.map((week, weekNo) => {
  				if(weekNo === 1) {
  					this.labels.push(
  						makeText('domain-name', x, monthNameHeight, getMonthName(index, true).toUpperCase(),
  							{
  								fontSize: 9
  							}
  						)
  					);
  				}
  				week.map((day, i) => {
  					if(day.fill) {
  						let data = {
  							'data-date': day.yyyyMmDd,
  							'data-value': day.dataValue,
  							'data-day': i
  						};
  						let square = heatSquare('day', x, y, squareSize, radius, day.fill, data);
  						this.serializedSubDomains.push(square);
  					}
  					y += rowHeight;
  				});
  				y = 0;
  				x += colWidth;
  			});
  
  			return this.serializedSubDomains;
  		},
  
  		animateElements(newData) {
  			if(newData) return [];
  		}
  	},
  
  	barGraph: {
  		layerClass: function() { return 'dataset-units dataset-bars dataset-' + this.constants.index; },
  		makeElements(data) {
  			let c = this.constants;
  			this.unitType = 'bar';
  			this.units = data.yPositions.map((y, j) => {
  				return datasetBar(
  					data.xPositions[j],
  					y,
  					data.barWidth,
  					resolveBarColor(data.colors || this.constants.color, j),
  					data.labels[j] + (window.percentages ? '%' : ''),
  					j,
  					data.offsets[j],
  					{
  						zeroLine: data.zeroLine,
  						barsWidth: data.barsWidth,
  						minHeight: c.minHeight
  					}
  				);
  			});
  			return this.units;
  		},
  		animateElements(newData) {
  			let newXPos = newData.xPositions;
  			let newYPos = newData.yPositions;
  			let newOffsets = newData.offsets;
  			let newLabels = newData.labels;
  
  			let oldXPos = this.oldData.xPositions;
  			let oldYPos = this.oldData.yPositions;
  			let oldOffsets = this.oldData.offsets;
  			let oldLabels = this.oldData.labels;
  
  			[oldXPos, newXPos] = equilizeNoOfElements(oldXPos, newXPos);
  			[oldYPos, newYPos] = equilizeNoOfElements(oldYPos, newYPos);
  			[oldOffsets, newOffsets] = equilizeNoOfElements(oldOffsets, newOffsets);
  			[oldLabels, newLabels] = equilizeNoOfElements(oldLabels, newLabels);
  
  			this.render({
  				xPositions: oldXPos,
  				yPositions: oldYPos,
  				offsets: oldOffsets,
  				labels: newLabels,
  				colors: newData.colors,
  
  				zeroLine: this.oldData.zeroLine,
  				barsWidth: this.oldData.barsWidth,
  				barWidth: this.oldData.barWidth,
  			});
  
  			let animateElements = [];
  
  			this.store.map((bar, i) => {
  				animateElements = animateElements.concat(animateBar(
  					bar, newXPos[i], newYPos[i], newData.barWidth, newOffsets[i],
  					{zeroLine: newData.zeroLine}
  				));
  			});
  
  			return animateElements;
  		}
  	},
  
  	lineGraph: {
  		layerClass: function() { return 'dataset-units dataset-line dataset-' + this.constants.index; },
  		makeElements(data) {
  			let c = this.constants;
  			this.unitType = 'dot';
  			this.paths = {};
  			if(!c.hideLine) {
  				this.paths = getPaths(
  					data.xPositions,
  					data.yPositions,
  					c.color,
  					{
  						heatline: c.heatline,
  						regionFill: c.regionFill,
  						spline: c.spline
  					},
  					{
  						svgDefs: c.svgDefs,
  						zeroLine: data.zeroLine
  					}
  				);
  			}
  
  			this.units = [];
  			if(!c.hideDots) {
  				this.units = data.yPositions.map((y, j) => {
  					return datasetDot(
  						data.xPositions[j],
  						y,
  						data.radius,
  						c.color,
  						(c.valuesOverPoints ? data.values[j] : ''),
  						j
  					);
  				});
  			}
  
  			return Object.values(this.paths).concat(this.units);
  		},
  		animateElements(newData) {
  			let newXPos = newData.xPositions;
  			let newYPos = newData.yPositions;
  			let newValues = newData.values;
  
  			let oldXPos = this.oldData.xPositions;
  			let oldYPos = this.oldData.yPositions;
  			let oldValues = this.oldData.values;
  
  			[oldXPos, newXPos] = equilizeNoOfElements(oldXPos, newXPos);
  			[oldYPos, newYPos] = equilizeNoOfElements(oldYPos, newYPos);
  			[oldValues, newValues] = equilizeNoOfElements(oldValues, newValues);
  
  			this.render({
  				xPositions: oldXPos,
  				yPositions: oldYPos,
  				values: newValues,
  
  				zeroLine: this.oldData.zeroLine,
  				radius: this.oldData.radius,
  			});
  
  			let animateElements = [];
  
  			if(Object.keys(this.paths).length) {
  				animateElements = animateElements.concat(animatePath(
  					this.paths, newXPos, newYPos, newData.zeroLine, this.constants.spline));
  			}
  
  			if(this.units.length) {
  				this.units.map((dot, i) => {
  					animateElements = animateElements.concat(animateDot(
  						dot, newXPos[i], newYPos[i]));
  				});
  			}
  
  			return animateElements;
  		}
  	}
  };
  
  export function getComponent(name, constants, getData) {
  	let keys = Object.keys(componentConfigs).filter(k => name.includes(k));
  	let config = componentConfigs[keys[0]];
  	Object.assign(config, {
  		constants: constants,
  		getData: getData
  	});
  	return new ChartComponent(config);
  }
  