/**
 * Copyright (c) 2017 ~ present NAVER Corp.
 * billboard.js project is licensed under the MIT license
 * @ignore
 */
import {scaleLinear as d3ScaleLinear} from "d3-scale";
import {isDefined, isNumber, isString} from "../../module/util";
import {d3Selection} from "../../../types/types";

export default class AxisRendererHelper {
	private owner;
	private config;
	private scale;

	constructor(owner) {
		const scale = d3ScaleLinear();
		const {config, params} = owner;

		this.owner = owner;
		this.config = config;
		this.scale = scale;

		if (config.noTransition || !params.config.transition_duration) {
			config.withoutTransition = true;
		}

		// set range
		config.range = this.scaleExtent((params.orgXScale || scale).range());
	}

	/**
	 * Compute a character dimension
	 * @param {d3.selection} node <g class=tick> node
	 * @returns {{w: number, h: number}}
	 * @private
	 */
	static getSizeFor1Char(node?) {
		// default size for one character
		const size = {
			w: 5.5,
			h: 11.5
		};

		!node.empty() && node.select("text")
			.text("0")
			.call(el => {
				try {
					const {width, height} = el.node().getBBox();

					if (width && height) {
						size.w = width;
						size.h = height;
					}
				} catch (e) {
				} finally {
					el.text("");
				}
			});

		this.getSizeFor1Char = () => size;

		return size;
	}

	/**
	 * Get tick transform setter function
	 * @param {string} id Axis id
	 * @returns {Function} transfrom setter function
	 * @private
	 */
	getTickTransformSetter(id: string): (selection: d3Selection, scale) => void {
		const {config} = this;
		const fn = id === "x" ?
			value => `translate(${value + config.tickOffset},0)` :
			value => `translate(0,${value})`;

		return (selection, scale) => {
			selection.attr("transform", d => fn(Math.ceil(scale(d))));
		};
	}

	scaleExtent(domain: [number, number]): [number, number] {
		const start = domain[0];
		const stop = domain[domain.length - 1];

		return start < stop ? [start, stop] : [stop, start];
	}

	generateTicks(scale, isYAxes: boolean): number[] {
		const {tickStepSize} = this.owner.params;
		let ticks: number[] = [];

		// When 'axis[y|y2].tick.stepSize' option is set
		if (isYAxes && tickStepSize) {
			const [start, end] = scale.domain();
			let interval = start;

			while (interval <= end) {
				ticks.push(interval);
				interval += tickStepSize;
			}
		} else if (scale.ticks) {
			ticks = scale.ticks(
				...(this.config.tickArguments || [])
			).map(v => (
				// round the tick value if is number
				(isString(v) && isNumber(v) && !isNaN(v) &&
					Math.round(v * 10) / 10
				) || v
			));
		} else {
			const domain = scale.domain();

			for (let i = Math.ceil(domain[0]); i < domain[1]; i++) {
				ticks.push(i);
			}

			if (ticks.length > 0 && ticks[0] > 0) {
				ticks.unshift(ticks[0] - (ticks[1] - ticks[0]));
			}
		}

		return ticks;
	}

	copyScale() {
		const newScale = this.scale.copy();

		if (!newScale.domain().length) {
			newScale.domain(this.scale.domain());
		}

		return newScale;
	}

	textFormatted(v: string | number | any): string {
		const tickFormat = this.config.tickFormat;

		// to round float numbers from 'binary floating point'
		// https://en.wikipedia.org/wiki/Double-precision_floating-point_format
		// https://stackoverflow.com/questions/17849101/laymans-explanation-for-why-javascript-has-weird-floating-math-ieee-754-stand
		const value = /\d+\.\d+0{5,}\d$/.test(v) ? +String(v).replace(/0+\d$/, "") : v;
		const formatted = tickFormat ? tickFormat(value) : value;

		return isDefined(formatted) ? formatted : "";
	}

	transitionise(selection): d3Selection {
		const {config} = this;

		return config.withoutTransition ?
			selection.interrupt() : selection.transition(config.transition);
	}
}
