"use strict"

class NumberObject {
	constructor(value = "") {
		this._value = value.toString()
		if (this._value.startsWith('++') || this._value.startsWith('--')) this._value = '+' + this._value.substr(2)
		else if (this._value.startsWith('-+') || this._value.startsWith('+-')) this._value = '-' + this._value.substr(2)

		const parser = /^(@)?([+\-]?(?:\d+|\d*\.\d+))(%)?(?:-([+\-]?(?:\d+|\d*\.\d+))(%)?)?$/
		//const parser = /^(@)?(?:\(([\d\w\s+\-*/%^\.]+)\)|([+\-]?(?:\d+|\d*\.\d+)))(%)?(?:-([+\-]?(?:\d+|\d*\.\d+))(%)?)?$/
		// $$$$ new parts[2] => exp
		const parts = parser.exec(this._value) || []

		this._fixed = parts[1] === '@'
		this._min = Number(parts[2]) || 0
		this._range = parts[4] !== undefined
		this._max = this._range ? Number(parts[4]) : this._min
		this._percent = Boolean(parts[3] || parts[5])
		this._valid = parts.length === 6

		if (this._max < this._min) {
			const tmp = this._max
			this._max = this._min
			this._min = tmp
		}
		if (this._percent) {
			this._min /= 100
			this._max /= 100
		}
		this.__fixed = this._fixed
		this.__min = this._min
		this.__max = this._max
		this.__percent = this._percent
	}

	isValid() {
		return this._valid
	}
	isFixed() {
		return this._fixed
	}
	isPercent(check100) {
		return check100 ? this._percent && this._min === 1 && this._max === 1 : this._percent
	}
	wasPercent(check100) {
		return check100 ? this.__percent && this.__min === 1 && this.__max === 1 : this.__percent
	}
	getMin() {
		return this._min
	}
	getMax() {
		return this._max
	}
	toString() {
		return this._value
	}
	toJSON(name, precision) {
		return this.resolve(precision) + (this.__percent || this._range ? ` (${this._value})` : '')
	}
	toDecimal(maxPrecision = 8) {
		return toDecimal(this._range ? randomFloat(this._min, this._max) : this._max, maxPrecision)
	}
	_() { // reset()
		this._fixed = this.__fixed
		this._min = this.__min
		this._max = this.__max
		this._percent = this.__percent
		return this
	}

	static validate(n) {
		if (isNaN(n)) {
			throw new TypeError(`Invalid number ${n} provided! (${typeof n})`)
		}
		return Number(n)
	}
	add(n) {
		n = NumberObject.validate(n)
		this._min += n
		this._max += n
		return this
	}
	sub(n) {
		n = NumberObject.validate(n)
		this._min -= n
		this._max -= n
		return this
	}
	mul(n) {
		n = NumberObject.validate(n)
		this._min *= n
		this._max *= n
		return this
	}
	div(n) {
		n = NumberObject.validate(n)
		this._min /= n
		this._max /= n
		return this
	}
	highest(n) {
		n = NumberObject.validate(n)
		if (this._max > n) {
			this._max = n
			this._min = Math.min(this._min, n)
		}
		return this
	}
	lowest(n) {
		n = NumberObject.validate(n)
		if (this._min < n) {
			this._max = Math.max(this._max, n)
			this._min = n
		}
		return this
	}
	minmax(min, max) {
		this._min = Math.max(Math.min(this._min, max || Number.MAX_VALUE), min || 0)
		this._max = Math.max(Math.min(this._max, max || Number.MAX_VALUE), min || 0)
		return this
	}
	abs() {
		this._min = Math.abs(this._min)
		this._max = Math.abs(this._max)
		return this
	}
	compare(n) {
		n = NumberObject.validate(n)
		return n < this._min ? -1 : n > this._max ? 1 : 0
	}
	reference(n) {
		if (this._percent) {
			this.mul(n)
			this._percent = false
		} else if (this._fixed) {
			this.sub(n)
			this._fixed = false
		}
		return this
	}
	relative(n) {
		if (!this._fixed) {
			this.reference(n)
			this.add(n)
		}
		return this
	}

	resolve(precision = 4) {
		const decimals = Math.max(0, parseInt(precision))
		let stepping = new BigNumber(precision)
		stepping = stepping.minus(decimals)

		let result = new BigNumber(this._range ? randomFloat(this._min, this._max) : this._max)
		if (stepping.comparedTo(0) !== 0) {
			result = result.div(stepping)
			result = result.decimalPlaces(0)
			result = result.times(stepping)
		}
		return result.toFixed(decimals, BigNumber.ROUND_DOWN)
	}
}
