/**
 * charts.js
 * load google charts based on table element
 */

import ElementHelpers from '../helpers/element-helpers';
import { GoogleChartsApi } from './charts/google-charts-api';

/**
* Declare constants
*/
const attributeBase = 'data-rs-charts';

export class Charts {

	constructor(element) {
		this.element = element;
		this.type = this.getType();
		this.initializeChart();
	}

	/**
	* Declare types
	*/
	get types() {
		return {
			column: 'column',
			donut: 'donut',
			line: 'line'
		}
	}

	/**
	* Declare attributes
	*/
	get attributes() {
		return {
			placeholder: `${attributeBase}-placeholder`,
			source: `${attributeBase}-source`,
			legend: `${attributeBase}-legend`,
			legendItem: `${attributeBase}-legend-item`
		}
	}

	/**
	 * Get type - get type of chart from base attribute
	 */
	getType() {
		let type = null;
		const typeRaw = this.element.getAttribute(attributeBase);
		if (typeof typeRaw === 'string'
			&& typeof this.types[typeRaw.toLowerCase()] !== 'undefined') {
			type = this.types[typeRaw.toLowerCase()]
		}
		if (type === null) {
			throw Error('Chart type is invalid');
		}
		return type;
	}

	/**
	 * Initialize chart - Load Google Maps API
	 */
	initializeChart() {
		// get elements
		this.chartElement = ElementHelpers.getElementByAttributeWithinElement(this.element, this.attributes.placeholder);
		// create map, controls and markers after the API is ready
		const chartsApi = new GoogleChartsApi();
		chartsApi.load().then(() => {
			this.createChart();
			this.createLegend();
		}).catch((reason) => {
			console.error(reason);
		});
	}

	/**
	 * Create chart - create Google Visualization chart based on type
	 */
	createChart() {
		// get source element
		const sourceElement = ElementHelpers.getElementByAttributeWithinElement(this.element, this.attributes.source);

		// create Google Visualization chart based on type
		switch (this.type) {
			case this.types.column:
				this.chart = new google.visualization.ColumnChart(this.chartElement);
				this.options = this.getColumnOptions();
				break;
			case this.types.donut:
				this.chart = new google.visualization.PieChart(this.chartElement);
				this.options = this.getDonutOptions();
				break;
			case this.types.line:
				this.chart = new google.visualization.LineChart(this.chartElement);
				this.options = this.getLineOptions();
				break;
		}
		// draw chart
		this.data = this.getData(sourceElement);
		this.chart.draw(this.data, this.options);
	}

	/**
	 * Get data - parse data from source table in to data table
	 * @param sourceTable
	 */
	getData(sourceTable) {
		this.sourceData = this.parseData(sourceTable);
		let data = google.visualization.arrayToDataTable(this.sourceData);
		return data;
	}

	/**
	 * Parse data from table to array
	 * @param sourceTable
	 */
	parseData(sourceTable) {
		let sourceData = [];

		// parse header text from th nodes and convert to array
		const thNodes = [...sourceTable.querySelectorAll('thead th')];
		let headers = thNodes.map((thNode) => {
			return thNode.innerText
		});
		// push header with 'string' and 'style' for column and donut chart
		if (this.type !== this.types.line) {
			headers.push({ type: 'string', role: 'style' });
		}
		sourceData.push(headers);

		// parse graph data from body
		let trNodes = [...sourceTable.querySelectorAll('tbody tr')];
		trNodes.forEach((trNode, index) => {
			let row = [...trNode.querySelectorAll('td')].map((tdNode, index) => {
				// first item is used for display, other are used as values
				return index === 0
					? tdNode.innerText
					: parseInt(tdNode.innerText);
			})
			// push 'color' row for column and donut chart
			if (this.type !== this.types.line) {
				row.push('color: ' + this.options.colors[index]);
			}
			sourceData.push(row);
		});
		return sourceData;
	}

	/**
	 * Get default options -  options for all charts
	 */
	getDefaultOptions() {
		return {
			backgroundColor: 'transparent',
			colors: ['#f0f0f0', '#e8e8e8', '#e0e0e0', '#dcdcdc', '#d8d8d8', '#d3d3d3', '#d0d0d0', '#c8c8c8',
				'#c0c0c0', '#bebebe', '#b8b8b8', '#b0b0b0', '#a9a9a9', '#a8a8a8', '#a0a0a0'],
			fontSize: 18,
			legend: { position: 'none' },
			tooltip: {
				textStyle: {
					color: '#000000',
					bold: false
				},
				isHtml: false,
				trigger: 'selection'
			}
		};
	}

	/**
	 * Get options for column chart
	 */
	getColumnOptions() {
		let options = this.getDefaultOptions();
		const chartWidth = this.chartElement.offsetWidth;
		const chartHeight = this.chartElement.offsetHeight;

		options.chartArea = { width: chartWidth - 50, left: 50, top: 20, height: chartHeight - 100 };
		options.hAxis = { textPosition: 'none' }
		return options;
	}

	/**
	 * Get options for donut chart
	 */
	getDonutOptions() {
		let options = this.getDefaultOptions();
		const chartWidth = this.chartElement.offsetWidth;

		options.chartArea = { width: chartWidth, left: 0, top: 0, height: chartWidth };
		options.height = chartWidth + 70;
		options.width = chartWidth;
		options.pieHole = .5;
		options.pieSliceBorderColor = 'transparent';
		return options;
	}

	/**
	 * Get options for line chart
	 */
	getLineOptions() {
		let options = this.getDefaultOptions();
		const chartWidth = this.chartElement.offsetWidth;
		const chartHeight = this.chartElement.offsetHeight;

		options.chartArea = { width: chartWidth - 50, left: 50, top: 20, height: chartHeight - 130 };
		options.curveType = 'function';
		return options;
	}

	/**
	 * Create legend - create customized legend 
	 */
	createLegend() {
		// get legend element
		const legend = ElementHelpers.getElementByAttributeWithinElement(this.element, this.attributes.legend);

		// create legend based on chart type
		switch (this.type) {
			case this.types.donut:
			case this.types.column:
				this.createDonutOrColumnLegend(legend);
				break;
			case this.types.line:
				this.createLineLegend(legend);
				break;
		}
	}

	/**
	 * Create legend for donut or column chart
	 * @param legend
	 */
	createDonutOrColumnLegend(legend) {
		// delete irrelevant first object from source data array
		this.sourceData.shift();
		// bind each value to li item
		this.sourceData.forEach((dataArray, index) => {
			if (legend !== null) {
				this.appendListItemsToList(index, legend, dataArray[0]);
			}
		});
	}

	/**
	 * Create legend for line chart
	 * @param legend
	 */
	createLineLegend(legend) {
		// delete irrelevant first object from source data array
		this.sourceData[0].shift();
		// bind each value to li item
		this.sourceData[0].forEach((string, index) => {
			if (legend !== null) {
				this.appendListItemsToList(index, legend, string);
			}
		});
	}

	/**
	 * Append list items to list - create li node and add to list
	 * @param index
	 * @param list
	 * @param listItemText
	 */
	appendListItemsToList(index, list, listItemText) {
		// create a <li> node
		const node = document.createElement('li');                 
		node.setAttribute(this.attributes.legendItem, this.options.colors[index]);
		// create a text node
		const textnode = document.createTextNode(listItemText);
		node.appendChild(textnode);
		list.appendChild(node);
	}

	/**
	 * Get selector
	 */
	static getSelector() {
		return `[${attributeBase}]`;
	}
}
